การสืบทอด / ต้นแบบหลายรายการใน JavaScript


132

ฉันมาถึงจุดที่ฉันต้องมีการสืบทอดหลายขั้นพื้นฐานบางอย่างเกิดขึ้นใน JavaScript (ฉันไม่ได้มาที่นี่เพื่อพูดคุยว่านี่เป็นความคิดที่ดีหรือไม่ดังนั้นโปรดเก็บความคิดเห็นเหล่านั้นไว้กับตัวเอง)

ฉันแค่อยากรู้ว่ามีใครพยายามทำสิ่งนี้ด้วยความสำเร็จ (หรือไม่) และพวกเขาไปถึงมันได้อย่างไร

ในการต้มมันลงสิ่งที่ฉันต้องการจริงๆคือต้องสามารถมีวัตถุที่สามารถสืบทอดคุณสมบัติจากห่วงโซ่ต้นแบบมากกว่าหนึ่งชิ้น(กล่าวคือแต่ละต้นแบบอาจมีห่วงโซ่ที่เหมาะสมของตัวเอง) แต่ตามลำดับความสำคัญ (มันจะ ค้นหาโซ่ตามลำดับสำหรับคำจำกัดความแรก)

เพื่อแสดงให้เห็นว่าสิ่งนี้เป็นไปได้อย่างไรในทางทฤษฎีสามารถทำได้โดยการติดโซ่รองเข้าที่ส่วนท้ายของห่วงโซ่หลัก แต่สิ่งนี้จะส่งผลต่ออินสแตนซ์ทั้งหมดของต้นแบบก่อนหน้านี้และนั่นไม่ใช่สิ่งที่ฉันต้องการ

คิด?


1
ฉันคิดว่าdojo ประกาศจัดการมรดกหลายรายการsrcเช่นกันฉันรู้สึกว่า mootools ทำเช่นกันส่วนใหญ่เกินกว่าฉัน แต่ฉันจะอ่านเรื่องนี้อย่างรวดเร็วตามที่ dojo แนะนำ
TI

ให้ดู TraitsJS (กลิงค์ 1 , การเชื่อมโยง 2 ) ก็เป็นทางเลือกที่ดีจริงๆมรดกหลายและ mixins ...
CMS

1
@Pointy เพราะมันไม่ค่อยมีพลวัต ฉันต้องการรับการเปลี่ยนแปลงที่เกิดขึ้นกับเครือข่ายหลักที่เกิดขึ้น อย่างไรก็ตามที่กล่าวมาฉันอาจต้องใช้วิธีนี้หากเป็นไปไม่ได้
devios1


1
สิ่งที่น่าสนใจอ่านเกี่ยวกับเรื่องนี้: webreflection.blogspot.co.uk/2009/06/…
โนบิตะ

คำตอบ:


49

มรดกหลายสามารถทำได้ใน ECMAScript 6 โดยใช้วัตถุพร็อกซี่

การดำเนินงาน

function getDesc (obj, prop) {
  var desc = Object.getOwnPropertyDescriptor(obj, prop);
  return desc || (obj=Object.getPrototypeOf(obj) ? getDesc(obj, prop) : void 0);
}
function multiInherit (...protos) {
  return Object.create(new Proxy(Object.create(null), {
    has: (target, prop) => protos.some(obj => prop in obj),
    get (target, prop, receiver) {
      var obj = protos.find(obj => prop in obj);
      return obj ? Reflect.get(obj, prop, receiver) : void 0;
    },
    set (target, prop, value, receiver) {
      var obj = protos.find(obj => prop in obj);
      return Reflect.set(obj || Object.create(null), prop, value, receiver);
    },
    *enumerate (target) { yield* this.ownKeys(target); },
    ownKeys(target) {
      var hash = Object.create(null);
      for(var obj of protos) for(var p in obj) if(!hash[p]) hash[p] = true;
      return Object.getOwnPropertyNames(hash);
    },
    getOwnPropertyDescriptor(target, prop) {
      var obj = protos.find(obj => prop in obj);
      var desc = obj ? getDesc(obj, prop) : void 0;
      if(desc) desc.configurable = true;
      return desc;
    },
    preventExtensions: (target) => false,
    defineProperty: (target, prop, desc) => false,
  }));
}

คำอธิบาย

วัตถุพร็อกซีประกอบด้วยวัตถุเป้าหมายและกับดักบางอย่างซึ่งกำหนดลักษณะการทำงานที่กำหนดเองสำหรับการดำเนินการพื้นฐาน

Object.create(obj)เมื่อมีการสร้างวัตถุซึ่งสืบทอดมาจากอีกคนหนึ่งที่เราใช้ แต่ในกรณีนี้เราต้องการการสืบทอดหลายรายการดังนั้นแทนที่จะเป็นobjใช้พร็อกซีที่จะเปลี่ยนเส้นทางการดำเนินการพื้นฐานไปยังวัตถุที่เหมาะสม

ฉันใช้กับดักเหล่านี้:

  • hasกับดักเป็นกับดักสำหรับการประกอบการin ฉันใช้someเพื่อตรวจสอบว่าอย่างน้อยหนึ่งต้นแบบมีคุณสมบัติหรือไม่
  • getกับดักเป็นกับดักสำหรับการได้รับค่าทรัพย์สิน ฉันใช้findเพื่อค้นหาต้นแบบแรกที่มีคุณสมบัตินั้นและฉันส่งคืนค่าหรือเรียก getter บนตัวรับที่เหมาะสม Reflect.getนี้จะถูกจัดการโดย undefinedถ้าไม่มีต้นแบบมีคุณสมบัติที่ฉันกลับมา
  • setกับดักเป็นกับดักสำหรับการตั้งค่าสถานที่ให้บริการ ฉันใช้findเพื่อค้นหาต้นแบบแรกที่มีคุณสมบัตินั้นและฉันเรียกตัวตั้งค่าบนตัวรับที่เหมาะสม หากไม่มีตัวตั้งค่าหรือไม่มีต้นแบบที่มีคุณสมบัติค่าจะถูกกำหนดบนตัวรับที่เหมาะสม Reflect.setนี้จะถูกจัดการโดย
  • enumerateกับดักเป็นกับดักสำหรับลูปfor...in ฉันทำซ้ำคุณสมบัติที่แจกแจงได้จากต้นแบบแรกจากนั้นจากตัวที่สองและอื่น ๆ เมื่อคุณสมบัติได้รับการทำซ้ำแล้วฉันจะเก็บไว้ในตารางแฮชเพื่อหลีกเลี่ยงการทำซ้ำอีกครั้ง
    คำเตือน : กับดักนี้ถูกลบออกใน ES7 แบบร่างและเลิกใช้งานแล้วในเบราว์เซอร์
  • ownKeysกับดักObject.getOwnPropertyNames()เป็นกับดักสำหรับ ตั้งแต่ ES7for...inลูปยังคงเรียก [[GetPrototypeOf]] และรับคุณสมบัติของแต่ละตัว ดังนั้นเพื่อที่จะทำให้มันวนซ้ำคุณสมบัติของต้นแบบทั้งหมดฉันใช้กับดักนี้เพื่อทำให้คุณสมบัติที่สืบทอดที่นับได้ทั้งหมดดูเหมือนคุณสมบัติของตัวเอง
  • getOwnPropertyDescriptorกับดักObject.getOwnPropertyDescriptor()เป็นกับดักสำหรับ ทำให้คุณสมบัติที่นับได้ทั้งหมดดูเหมือนคุณสมบัติของตัวเองในไฟล์ownKeysกับดักนั้นไม่เพียงพอfor...inลูปจะได้รับตัวอธิบายเพื่อตรวจสอบว่าสามารถแจกแจงได้หรือไม่ ดังนั้นฉันจึงใช้findเพื่อค้นหาต้นแบบแรกที่มีคุณสมบัตินั้นและฉันวนซ้ำห่วงโซ่ต้นแบบของมันจนกว่าฉันจะพบเจ้าของทรัพย์สินและฉันส่งคืนตัวอธิบาย undefinedถ้าไม่มีต้นแบบมีคุณสมบัติที่ฉันกลับมา ตัวอธิบายถูกแก้ไขเพื่อให้สามารถกำหนดค่าได้มิฉะนั้นเราอาจทำลายค่าคงที่ของพร็อกซีบางตัวได้
  • preventExtensionsและdefinePropertyกับดักที่จะถูกรวมเท่านั้นที่จะป้องกันไม่ให้การดำเนินงานเหล่านี้จากการปรับเปลี่ยนเป้าหมายของพร็อกซี่ มิฉะนั้นเราอาจทำลายค่าคงที่ของพร็อกซีบางตัวได้

มีกับดักมากขึ้นซึ่งฉันไม่ได้ใช้

  • getPrototypeOfดักอาจจะเพิ่ม แต่ไม่มีวิธีการที่เหมาะสมที่จะกลับต้นแบบหลาย โดยนัยนี้instanceofจะไม่ได้ผลเช่นกัน ดังนั้นฉันจึงปล่อยให้มันได้ต้นแบบของเป้าหมายซึ่งตอนแรกเป็นโมฆะ
  • setPrototypeOfดักอาจจะเพิ่มและยอมรับอาร์เรย์ของวัตถุซึ่งจะเข้ามาแทนที่ต้นแบบ เหลือไว้เป็นแบบฝึกหัดสำหรับผู้อ่าน ที่นี่ฉันแค่ปล่อยให้มันปรับเปลี่ยนต้นแบบของเป้าหมายซึ่งไม่มีประโยชน์มากนักเพราะไม่มีกับดักใช้เป้าหมาย
  • deletePropertyกับดักเป็นกับดักสำหรับการลบคุณสมบัติตัวเอง พร็อกซีแสดงถึงการถ่ายทอดทางพันธุกรรมดังนั้นสิ่งนี้จึงไม่สมเหตุสมผล ฉันปล่อยให้มันพยายามลบเป้าหมายซึ่งไม่ควรมีคุณสมบัติอยู่แล้ว
  • isExtensibleกับดักเป็นกับดักสำหรับการขยายการ ไม่มีประโยชน์มากนักเนื่องจากความไม่แปรเปลี่ยนบังคับให้มันคืนความสามารถในการขยายเดียวกันกับเป้าหมาย ดังนั้นฉันแค่ปล่อยให้มันเปลี่ยนเส้นทางการทำงานไปยังเป้าหมายซึ่งจะขยายได้
  • applyและconstructกับดักกับดักสำหรับการโทรหรืออินสแตนซ์ จะมีประโยชน์ก็ต่อเมื่อเป้าหมายเป็นฟังก์ชันหรือตัวสร้าง

ตัวอย่าง

// Creating objects
var o1, o2, o3,
    obj = multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3});

// Checking property existences
'a' in obj; // true   (inherited from o1)
'b' in obj; // true   (inherited from o2)
'c' in obj; // false  (not found)

// Setting properties
obj.c = 3;

// Reading properties
obj.a; // 1           (inherited from o1)
obj.b; // 2           (inherited from o2)
obj.c; // 3           (own property)
obj.d; // undefined   (not found)

// The inheritance is "live"
obj.a; // 1           (inherited from o1)
delete o1.a;
obj.a; // 3           (inherited from o3)

// Property enumeration
for(var p in obj) p; // "c", "b", "a"

1
ไม่มีปัญหาด้านประสิทธิภาพบางอย่างที่จะเกี่ยวข้องกับการใช้งานในระดับปกติหรือไม่?
Tomáš Zato - คืนสถานะ Monica

1
@ TomášZatoมันจะช้ากว่าคุณสมบัติข้อมูลในออบเจ็กต์ปกติ แต่ฉันไม่คิดว่ามันจะแย่ไปกว่าคุณสมบัติของ accessor มากนัก
Oriol

TIL:multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3})
bloodyKnuckles

4
ฉันจะพิจารณาแทนที่ "การสืบทอดหลายรายการ" โดย "การมอบสิทธิ์หลายรายการ" เพื่อให้เข้าใจถึงสิ่งที่เกิดขึ้นได้ดีขึ้น แนวคิดหลักในการใช้งานของคุณคือพร็อกซีกำลังเลือกวัตถุที่เหมาะสมในการมอบหมาย (หรือส่งต่อ) ข้อความ พลังของโซลูชันของคุณคือคุณสามารถขยายต้นแบบเป้าหมายได้แบบไดนามิก คำตอบอื่น ๆ คือการใช้การเรียงต่อกัน (ala Object.assign) หรือการสร้างกราฟที่ค่อนข้างแตกต่างกันในที่สุดคำตอบทั้งหมดก็ได้รับห่วงโซ่ต้นแบบแบบหนึ่งเดียวระหว่างวัตถุ โซลูชันพร็อกซีนำเสนอการแตกแขนงของรันไทม์และสิ่งนี้ก็น่าสนใจ!
sminutoli

เกี่ยวกับประสิทธิภาพหากคุณสร้างออบเจ็กต์ที่สืบทอดมาจากหลายอ็อบเจ็กต์ซึ่งสืบทอดมาจากหลายอ็อบเจ็กต์และอื่น ๆ สิ่งนั้นจะกลายเป็นเอ็กซ์โพเนนเชียล ใช่มันจะช้าลง แต่ในกรณีปกติฉันไม่คิดว่ามันจะแย่ขนาดนั้น
Oriol

16

อัปเดต (2019):โพสต์ต้นฉบับค่อนข้างล้าสมัย บทความนี้ (ตอนนี้ลิงก์เก็บถาวรทางอินเทอร์เน็ตเนื่องจากโดเมนหายไป) และไลบรารี GitHub ที่เกี่ยวข้องเป็นแนวทางที่ทันสมัย

โพสต์ต้นฉบับ: การสืบทอดหลายรายการ [แก้ไขไม่ใช่การสืบทอดประเภทที่เหมาะสม แต่เป็นคุณสมบัติ; mixins] ใน Javascript ค่อนข้างตรงไปตรงมาหากคุณใช้ต้นแบบที่สร้างขึ้นแทนที่จะใช้วัตถุทั่วไป นี่คือคลาสพาเรนต์สองคลาสที่จะสืบทอดจาก:

function FoodPrototype() {
    this.eat = function () {
        console.log("Eating", this.name);
    };
}
function Food(name) {
    this.name = name;
}
Food.prototype = new FoodPrototype();


function PlantPrototype() {
    this.grow = function () {
        console.log("Growing", this.name);
    };
}
function Plant(name) {
    this.name = name;
}
Plant.prototype = new PlantPrototype();

โปรดทราบว่าฉันใช้สมาชิก "ชื่อ" คนเดียวกันในแต่ละกรณีซึ่งอาจเป็นปัญหาได้หากผู้ปกครองไม่เห็นด้วยเกี่ยวกับวิธีจัดการ "ชื่อ" แต่มันเข้ากันได้ (ซ้ำซ้อนจริงๆ) ในกรณีนี้

ตอนนี้เราต้องการคลาสที่สืบทอดจากทั้งสองอย่าง การสืบทอดทำได้โดยการเรียกใช้ฟังก์ชันตัวสร้าง (โดยไม่ต้องใช้คีย์เวิร์ดใหม่) สำหรับต้นแบบและตัวสร้างอ็อบเจ็กต์ ขั้นแรกต้นแบบต้องสืบทอดจากต้นแบบแม่

function FoodPlantPrototype() {
    FoodPrototype.call(this);
    PlantPrototype.call(this);
    // plus a function of its own
    this.harvest = function () {
        console.log("harvest at", this.maturity);
    };
}

และตัวสร้างต้องสืบทอดจากคอนสตรัคเตอร์หลัก:

function FoodPlant(name, maturity) {
    Food.call(this, name);
    Plant.call(this, name);
    // plus a property of its own
    this.maturity = maturity;
}

FoodPlant.prototype = new FoodPlantPrototype();

ตอนนี้คุณสามารถเติบโตกินและเก็บเกี่ยวอินสแตนซ์ต่างๆ:

var fp1 = new FoodPlant('Radish', 28);
var fp2 = new FoodPlant('Corn', 90);

fp1.grow();
fp2.grow();
fp1.harvest();
fp1.eat();
fp2.harvest();
fp2.eat();

คุณสามารถทำสิ่งนี้โดยสร้างต้นแบบได้หรือไม่? (Array, String, Number)
Tomáš Zato - Reinstate Monica

ฉันไม่คิดว่าต้นแบบในตัวมีตัวสร้างที่คุณสามารถโทรหาได้
Roy J

ดีฉันสามารถทำได้แต่ก็ดูเหมือนจะไม่ส่งผลกระทบต่อสิ่งที่ฉันผ่านเป็นArray.call(...) this
Tomáš Zato - คืนสถานะ Monica

@ TomášZatoคุณทำได้Array.prototype.constructor.call()
Roy J

1
@AbhishekGupta ขอบคุณที่แจ้งให้เราทราบ ฉันได้แทนที่ลิงก์ด้วยลิงก์ไปยังหน้าเว็บที่เก็บถาวร
Roy J

7

อันนี้ใช้Object.createสร้างห่วงโซ่ต้นแบบจริง:

function makeChain(chains) {
  var c = Object.prototype;

  while(chains.length) {
    c = Object.create(c);
    $.extend(c, chains.pop()); // some function that does mixin
  }

  return c;
}

ตัวอย่างเช่น:

var obj = makeChain([{a:1}, {a: 2, b: 3}, {c: 4}]);

จะกลับมา:

a: 1
  a: 2
  b: 3
    c: 4
      <Object.prototype stuff>

เพื่อให้obj.a === 1, obj.b === 3ฯลฯ


เพียงคำถามสมมุติสั้น ๆ : ฉันต้องการสร้างคลาส Vector โดยผสมต้นแบบ Number และ Array (เพื่อความสนุกสนาน) สิ่งนี้จะให้ทั้งดัชนีอาร์เรย์และตัวดำเนินการทางคณิตศาสตร์ แต่มันจะได้ผล?
Tomáš Zato - คืนสถานะ Monica

@ TomášZatoคุณควรตรวจสอบบทความนี้หากคุณกำลังมองหาอาร์เรย์ย่อย จะช่วยให้คุณไม่ปวดหัว โชคดี!
user3276552

5

ฉันชอบการใช้โครงสร้างคลาสของ John Resig: http://ejohn.org/blog/simple-javascript-inheritance/

สิ่งนี้สามารถขยายได้ง่ายๆเช่น:

Class.extend = function(prop /*, prop, prop, prop */) {
    for( var i=1, l=arguments.length; i<l; i++ ){
        prop = $.extend( prop, arguments[i] );
    }

    // same code
}

ซึ่งจะช่วยให้คุณส่งผ่านวัตถุหลายชิ้นที่จะสืบทอด คุณจะสูญเสียinstanceOfความสามารถที่นี่ แต่นั่นเป็นสิ่งที่ได้รับหากคุณต้องการมรดกหลายรายการ


ตัวอย่างที่ค่อนข้างซับซ้อนของฉันข้างต้นมีอยู่ที่https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js

โปรดทราบว่ามีรหัสที่ตายแล้วในไฟล์นั้น แต่อนุญาตให้มีการสืบทอดได้หลายรายการหากคุณต้องการดู


หากคุณต้องการมรดกที่ถูกล่ามโซ่ (ไม่ใช่การสืบทอดหลาย ๆ อย่าง แต่สำหรับคนส่วนใหญ่มันเป็นสิ่งเดียวกัน) สามารถทำได้ด้วยคลาสเช่น:

var newClass = Class.extend( cls1 ).extend( cls2 ).extend( cls3 )

ซึ่งจะรักษาห่วงโซ่ต้นแบบเดิม แต่คุณจะมีโค้ดที่ไม่มีจุดหมายจำนวนมากที่ทำงานอยู่


7
ที่สร้างโคลนตื้นที่ผสาน การเพิ่มคุณสมบัติใหม่ให้กับอ็อบเจกต์ที่ "สืบทอด" จะไม่ทำให้คุณสมบัติใหม่ปรากฏบนอ็อบเจกต์ที่ได้รับมาเหมือนอย่างที่มันทำในการสืบทอดต้นแบบที่แท้จริง
Daniel Earwicker

@DanielEarwicker - จริง แต่ถ้าคุณต้องการ "การสืบทอดหลายรายการ" ในคลาสเดียวนั้นมาจากสองคลาสไม่มีทางเลือกอื่นจริงๆ คำตอบที่แก้ไขเพื่อสะท้อนให้เห็นว่าการเชื่อมโยงชั้นเรียนเข้าด้วยกันเป็นสิ่งเดียวกันในกรณีส่วนใหญ่
Mark Kahn

ดูเหมือนว่า GitHUb ของคุณจะหายไปแล้วคุณยังมีgithub.com/cwolves/Fetch/blob/master/support/plugins/klass/…ฉันจะไม่สนใจที่จะดูถ้าคุณสนใจที่จะแบ่งปัน?
JasonDavis

4

อย่าสับสนกับการใช้กรอบ JavaScript ของการสืบทอดหลาย ๆ

สิ่งที่คุณต้องทำคือใช้Object.create ()เพื่อสร้างอ็อบเจกต์ใหม่ทุกครั้งด้วยอ็อบเจ็กต์ต้นแบบและคุณสมบัติที่ระบุจากนั้นอย่าลืมเปลี่ยนObject.prototype.constructorในแต่ละขั้นตอนหากคุณวางแผนที่จะสร้างอินสแตนซ์Bใน อนาคต.

เพื่อสืบทอดคุณสมบัติของอินสแตนซ์thisAและthisBเราใช้Function.prototype.call ()ที่ส่วนท้ายของฟังก์ชันอ็อบเจ็กต์แต่ละฟังก์ชัน นี่เป็นทางเลือกหากคุณสนใจเฉพาะการสืบทอดต้นแบบ

เรียกใช้รหัสต่อไปนี้ที่ใดที่หนึ่งและสังเกตobjC:

function A() {
  this.thisA = 4; // objC will contain this property
}

A.prototype.a = 2; // objC will contain this property

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function B() {
  this.thisB = 55; // objC will contain this property

  A.call(this);
}

B.prototype.b = 3; // objC will contain this property

C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;

function C() {
  this.thisC = 123; // objC will contain this property

  B.call(this);
}

C.prototype.c = 2; // objC will contain this property

var objC = new C();
  • B สืบทอดต้นแบบมาจาก A
  • C สืบทอดต้นแบบมาจาก B
  • objC เป็นตัวอย่างของ C

นี่เป็นคำอธิบายที่ดีเกี่ยวกับขั้นตอนข้างต้น:

OOP ใน JavaScript: สิ่งที่คุณต้องรู้


สิ่งนี้ไม่ได้คัดลอกคุณสมบัติทั้งหมดไปยังวัตถุใหม่หรือไม่? ดังนั้นหากคุณมีสองต้นแบบคือ A และ B และคุณสร้างมันขึ้นมาใหม่ทั้งสองบน C การเปลี่ยนคุณสมบัติของ A จะไม่ส่งผลกระทบต่อทรัพย์สินนั้นใน C และในทางกลับกันวีซ่า คุณจะได้สำเนาคุณสมบัติทั้งหมดใน A และ B ที่เก็บไว้ในหน่วยความจำ มันจะเป็นประสิทธิภาพเช่นเดียวกับถ้าคุณได้เข้ารหัสคุณสมบัติทั้งหมดของ A และ B เป็น C อย่างหนักมันเป็นสิ่งที่ดีสำหรับความสามารถในการอ่านและการค้นหาคุณสมบัติไม่จำเป็นต้องเดินทางไปยังออบเจ็กต์หลัก แต่ไม่ใช่การสืบทอด - เหมือนการโคลนนิ่ง การเปลี่ยนคุณสมบัติบน A ไม่ได้เปลี่ยนคุณสมบัติที่โคลนบน C.
Frank

2

ฉันไม่ได้เป็นผู้เชี่ยวชาญด้าน javascript OOP แต่ถ้าฉันเข้าใจคุณถูกต้องคุณต้องการอะไรเช่น (รหัสหลอก):

Earth.shape = 'round';
Animal.shape = 'random';

Cat inherit from (Earth, Animal);

Cat.shape = 'random' or 'round' depending on inheritance order;

ในกรณีนั้นฉันจะลองทำสิ่งที่ชอบ:

var Earth = function(){};
Earth.prototype.shape = 'round';

var Animal = function(){};
Animal.prototype.shape = 'random';
Animal.prototype.head = true;

var Cat = function(){};

MultiInherit(Cat, Earth, Animal);

console.log(new Cat().shape); // yields "round", since I reversed the inheritance order
console.log(new Cat().head); // true

function MultiInherit() {
    var c = [].shift.call(arguments),
        len = arguments.length
    while(len--) {
        $.extend(c.prototype, new arguments[len]());
    }
}

1
นี่ไม่ใช่แค่การเลือกต้นแบบแรกและเพิกเฉยต่อส่วนที่เหลือ? การตั้งค่าc.prototypeหลาย ๆ ครั้งจะไม่ทำให้เกิดต้นแบบหลายตัว ตัวอย่างเช่นถ้าคุณมีAnimal.isAlive = true, Cat.isAliveจะยังคงไม่ได้กำหนด
devios1

ใช่ฉันหมายถึงการผสมต้นแบบแก้ไข ... (ฉันใช้การขยายของ jQuery ที่นี่ แต่คุณได้ภาพ)
David Hellsing

2

เป็นไปได้ที่จะใช้การสืบทอดหลายรายการใน JavaScript แม้ว่ามีห้องสมุดเพียงไม่กี่แห่งที่ทำเช่นนั้น

ฉันสามารถชี้Ring.jsเป็นตัวอย่างเดียวที่ฉันรู้


2

วันนี้ฉันทำงานมากและพยายามทำสิ่งนี้ด้วยตัวเองใน ES6 วิธีที่ฉันทำคือใช้ Browserify, Babel จากนั้นฉันทดสอบกับ Wallaby และดูเหมือนว่าจะได้ผล เป้าหมายของฉันคือการขยาย Array ปัจจุบันรวม ES6, ES7 และเพิ่มคุณสมบัติที่กำหนดเองเพิ่มเติมที่ฉันต้องการในต้นแบบเพื่อจัดการกับข้อมูลเสียง

วัลลาบีผ่านการทดสอบ 4 รายการของฉัน ไฟล์ example.js สามารถวางในคอนโซลและคุณจะเห็นว่าคุณสมบัติ "รวม" อยู่ในต้นแบบของคลาส ฉันยังต้องการทดสอบสิ่งนี้อีกในวันพรุ่งนี้

นี่คือวิธีการของฉัน: (ฉันมักจะ refactor และบรรจุหีบห่อใหม่เป็นโมดูลหลังจากการนอนหลับ!)

var includes = require('./polyfills/includes');
var keys =  Object.getOwnPropertyNames(includes.prototype);
keys.shift();

class ArrayIncludesPollyfills extends Array {}

function inherit (...keys) {
  keys.map(function(key){
      ArrayIncludesPollyfills.prototype[key]= includes.prototype[key];
  });
}

inherit(keys);

module.exports = ArrayIncludesPollyfills

Github Repo: https://github.com/danieldram/array-includes-polyfill


2

ฉันคิดว่ามันเป็นเรื่องง่ายอย่างน่าขัน ปัญหาคือชั้นลูกจะอ้างถึงinstanceofชั้นหนึ่งที่คุณเรียกเท่านั้น

https://jsfiddle.net/1033xzyt/19/

function Foo() {
  this.bar = 'bar';
  return this;
}
Foo.prototype.test = function(){return 1;}

function Bar() {
  this.bro = 'bro';
  return this;
}
Bar.prototype.test2 = function(){return 2;}

function Cool() {
  Foo.call(this);
  Bar.call(this);

  return this;
}

var combine = Object.create(Foo.prototype);
$.extend(combine, Object.create(Bar.prototype));

Cool.prototype = Object.create(combine);
Cool.prototype.constructor = Cool;

var cool = new Cool();

console.log(cool.test()); // 1
console.log(cool.test2()); //2
console.log(cool.bro) //bro
console.log(cool.bar) //bar
console.log(cool instanceof Foo); //true
console.log(cool instanceof Bar); //false

1

ตรวจสอบรหัสด้านล่างซึ่ง IS แสดงการรองรับการสืบทอดหลายรายการ ทำได้โดยใช้PROTOTYPAL INHERITANCE

function A(name) {
    this.name = name;
}
A.prototype.setName = function (name) {

    this.name = name;
}

function B(age) {
    this.age = age;
}
B.prototype.setAge = function (age) {
    this.age = age;
}

function AB(name, age) {
    A.prototype.setName.call(this, name);
    B.prototype.setAge.call(this, age);
}

AB.prototype = Object.assign({}, Object.create(A.prototype), Object.create(B.prototype));

AB.prototype.toString = function () {
    return `Name: ${this.name} has age: ${this.age}`
}

const a = new A("shivang");
const b = new B(32);
console.log(a.name);
console.log(b.age);
const ab = new AB("indu", 27);
console.log(ab.toString());

1

ฉันมีฟังก์ชั่นที่อนุญาตให้กำหนดคลาสด้วยการสืบทอดหลายรายการ อนุญาตให้ใช้รหัสดังต่อไปนี้ โดยรวมแล้วคุณจะสังเกตเห็นการออกจากเทคนิคการจำแนกแบบเนทีฟในจาวาสคริปต์ (เช่นคุณจะไม่เห็นclassคีย์เวิร์ด):

let human = new Running({ name: 'human', numLegs: 2 });
human.run();

let airplane = new Flying({ name: 'airplane', numWings: 2 });
airplane.fly();

let dragon = new RunningFlying({ name: 'dragon', numLegs: 4, numWings: 6 });
dragon.takeFlight();

เพื่อสร้างผลลัพธ์เช่นนี้:

human runs with 2 legs.
airplane flies away with 2 wings!
dragon runs with 4 legs.
dragon flies away with 6 wings!

คำจำกัดความของคลาสมีลักษณะดังนี้:

let Named = makeClass('Named', {}, () => ({
  init: function({ name }) {
    this.name = name;
  }
}));

let Running = makeClass('Running', { Named }, protos => ({
  init: function({ name, numLegs }) {
    protos.Named.init.call(this, { name });
    this.numLegs = numLegs;
  },
  run: function() {
    console.log(`${this.name} runs with ${this.numLegs} legs.`);
  }
}));

let Flying = makeClass('Flying', { Named }, protos => ({
  init: function({ name, numWings }) {
    protos.Named.init.call(this, { name });
    this.numWings = numWings;
  },
  fly: function( ){
    console.log(`${this.name} flies away with ${this.numWings} wings!`);
  }
}));

let RunningFlying = makeClass('RunningFlying', { Running, Flying }, protos => ({
  init: function({ name, numLegs, numWings }) {
    protos.Running.init.call(this, { name, numLegs });
    protos.Flying.init.call(this, { name, numWings });
  },
  takeFlight: function() {
    this.run();
    this.fly();
  }
}));

เราจะเห็นว่าแต่ละนิยามคลาสโดยใช้makeClassฟังก์ชันยอมรับObjectชื่อคลาสพาเรนต์ที่แมปกับคลาสพาเรนต์ นอกจากนี้ยังยอมรับฟังก์ชันที่ส่งคืนObjectคุณสมบัติที่มีสำหรับคลาสที่กำหนด ฟังก์ชันนี้มีพารามิเตอร์protosซึ่งมีข้อมูลเพียงพอที่จะเข้าถึงคุณสมบัติใด ๆ ที่กำหนดโดยคลาสพาเรนต์

ชิ้นส่วนสุดท้ายที่จำเป็นคือmakeClassฟังก์ชั่นเองซึ่งทำงานได้ไม่น้อย นี่คือรหัสที่เหลือ ฉันแสดงความคิดเห็นmakeClassค่อนข้างหนัก:

let makeClass = (name, parents={}, propertiesFn=()=>({})) => {
  
  // The constructor just curries to a Function named "init"
  let Class = function(...args) { this.init(...args); };
  
  // This allows instances to be named properly in the terminal
  Object.defineProperty(Class, 'name', { value: name });
  
  // Tracking parents of `Class` allows for inheritance queries later
  Class.parents = parents;
  
  // Initialize prototype
  Class.prototype = Object.create(null);
  
  // Collect all parent-class prototypes. `Object.getOwnPropertyNames`
  // will get us the best results. Finally, we'll be able to reference
  // a property like "usefulMethod" of Class "ParentClass3" with:
  // `parProtos.ParentClass3.usefulMethod`
  let parProtos = {};
  for (let parName in parents) {
    let proto = parents[parName].prototype;
    parProtos[parName] = {};
    for (let k of Object.getOwnPropertyNames(proto)) {
      parProtos[parName][k] = proto[k];
    }
  }
  
  // Resolve `properties` as the result of calling `propertiesFn`. Pass
  // `parProtos`, so a child-class can access parent-class methods, and
  // pass `Class` so methods of the child-class have a reference to it
  let properties = propertiesFn(parProtos, Class);
  properties.constructor = Class; // Ensure "constructor" prop exists
  
  // If two parent-classes define a property under the same name, we
  // have a "collision". In cases of collisions, the child-class *must*
  // define a method (and within that method it can decide how to call
  // the parent-class methods of the same name). For every named
  // property of every parent-class, we'll track a `Set` containing all
  // the methods that fall under that name. Any `Set` of size greater
  // than one indicates a collision.
  let propsByName = {}; // Will map property names to `Set`s
  for (let parName in parProtos) {
    
    for (let propName in parProtos[parName]) {
      
      // Now track the property `parProtos[parName][propName]` under the
      // label of `propName`
      if (!propsByName.hasOwnProperty(propName))
        propsByName[propName] = new Set();
      propsByName[propName].add(parProtos[parName][propName]);
      
    }
    
  }
  
  // For all methods defined by the child-class, create or replace the
  // entry in `propsByName` with a Set containing a single item; the
  // child-class' property at that property name (this also guarantees
  // there is no collision at this property name). Note property names
  // prefixed with "$" will be considered class properties (and the "$"
  // will be removed).
  for (let propName in properties) {
    if (propName[0] === '$') {
      
      // The "$" indicates a class property; attach to `Class`:
      Class[propName.slice(1)] = properties[propName];
      
    } else {
      
      // No "$" indicates an instance property; attach to `propsByName`:
      propsByName[propName] = new Set([ properties[propName] ]);
      
    }
  }
  
  // Ensure that "init" is defined by a parent-class or by the child:
  if (!propsByName.hasOwnProperty('init'))
    throw Error(`Class "${name}" is missing an "init" method`);
  
  // For each property name in `propsByName`, ensure that there is no
  // collision at that property name, and if there isn't, attach it to
  // the prototype! `Object.defineProperty` can ensure that prototype
  // properties won't appear during iteration with `in` keyword:
  for (let propName in propsByName) {
    let propsAtName = propsByName[propName];
    if (propsAtName.size > 1)
      throw new Error(`Class "${name}" has conflict at "${propName}"`);
    
    Object.defineProperty(Class.prototype, propName, {
      enumerable: false,
      writable: true,
      value: propsAtName.values().next().value // Get 1st item in Set
    });
  }
  
  return Class;
};

let Named = makeClass('Named', {}, () => ({
  init: function({ name }) {
    this.name = name;
  }
}));

let Running = makeClass('Running', { Named }, protos => ({
  init: function({ name, numLegs }) {
    protos.Named.init.call(this, { name });
    this.numLegs = numLegs;
  },
  run: function() {
    console.log(`${this.name} runs with ${this.numLegs} legs.`);
  }
}));

let Flying = makeClass('Flying', { Named }, protos => ({
  init: function({ name, numWings }) {
    protos.Named.init.call(this, { name });
    this.numWings = numWings;
  },
  fly: function( ){
    console.log(`${this.name} flies away with ${this.numWings} wings!`);
  }
}));

let RunningFlying = makeClass('RunningFlying', { Running, Flying }, protos => ({
  init: function({ name, numLegs, numWings }) {
    protos.Running.init.call(this, { name, numLegs });
    protos.Flying.init.call(this, { name, numWings });
  },
  takeFlight: function() {
    this.run();
    this.fly();
  }
}));

let human = new Running({ name: 'human', numLegs: 2 });
human.run();

let airplane = new Flying({ name: 'airplane', numWings: 2 });
airplane.fly();

let dragon = new RunningFlying({ name: 'dragon', numLegs: 4, numWings: 6 });
dragon.takeFlight();

makeClassฟังก์ชั่นนอกจากนี้ยังสนับสนุนคุณสมบัติระดับ; สิ่งเหล่านี้ถูกกำหนดโดยนำหน้าชื่อคุณสมบัติด้วย$สัญลักษณ์ (โปรดทราบว่าชื่อคุณสมบัติสุดท้ายที่ผลลัพธ์จะ$ถูกลบออก) ด้วยเหตุนี้เราจึงสามารถเขียนDragonคลาสพิเศษที่จำลอง "ประเภท" ของมังกรโดยที่รายชื่อประเภทมังกรที่มีอยู่จะถูกเก็บไว้ในคลาสนั้นเองซึ่งต่างจากในอินสแตนซ์:

let Dragon = makeClass('Dragon', { RunningFlying }, protos => ({

  $types: {
    wyvern: 'wyvern',
    drake: 'drake',
    hydra: 'hydra'
  },

  init: function({ name, numLegs, numWings, type }) {
    protos.RunningFlying.init.call(this, { name, numLegs, numWings });
    this.type = type;
  },
  description: function() {
    return `A ${this.type}-type dragon with ${this.numLegs} legs and ${this.numWings} wings`;
  }
}));

let dragon1 = new Dragon({ name: 'dragon1', numLegs: 2, numWings: 4, type: Dragon.types.drake });
let dragon2 = new Dragon({ name: 'dragon2', numLegs: 4, numWings: 2, type: Dragon.types.hydra });

ความท้าทายของการสืบทอดหลายอย่าง

ใครก็ตามที่ติดตามรหัสmakeClassอย่างใกล้ชิดจะสังเกตเห็นปรากฏการณ์ที่ไม่พึงปรารถนาที่ค่อนข้างสำคัญซึ่งเกิดขึ้นอย่างเงียบ ๆ เมื่อโค้ดด้านบนทำงาน: การสร้างอินสแตนซ์ a RunningFlyingจะส่งผลให้มีการเรียกสองครั้งไปยังNamedสร้าง!

เนื่องจากกราฟการสืบทอดมีลักษณะดังนี้:

 (^^ More Specialized ^^)

      RunningFlying
         /     \
        /       \
    Running   Flying
         \     /
          \   /
          Named

  (vv More Abstract vv)

เมื่อมี หลายพา ธ ไปยังคลาสพาเรนต์เดียวกันในกราฟการสืบทอดคลาสย่อยคลาสย่อยอินสแตนซ์ของคลาสย่อยจะเรียกคอนสตรัคเตอร์ระดับพาเรนต์นั้นหลายครั้ง

การต่อสู้แบบนี้ไม่ใช่เรื่องเล็กน้อย ลองดูตัวอย่างที่มีชื่อคลาสแบบง่าย เราจะพิจารณาคลาสAซึ่งเป็นคลาสพาเรนต์ที่เป็นนามธรรมที่สุดคลาสBและคลาสCที่สืบทอดมาจากAคลาสและคลาสBCที่สืบทอดมาจากBและC(และด้วยเหตุนี้จึงมีคอนเซ็ปต์ "double-inheritits" จากA):

let A = makeClass('A', {}, () => ({
  init: function() {
    console.log('Construct A');
  }
}));
let B = makeClass('B', { A }, protos => ({
  init: function() {
    protos.A.init.call(this);
    console.log('Construct B');
  }
}));
let C = makeClass('C', { A }, protos => ({
  init: function() {
    protos.A.init.call(this);
    console.log('Construct C');
  }
}));
let BC = makeClass('BC', { B, C }, protos => ({
  init: function() {
    // Overall "Construct A" is logged twice:
    protos.B.init.call(this); // -> console.log('Construct A'); console.log('Construct B');
    protos.C.init.call(this); // -> console.log('Construct A'); console.log('Construct C');
    console.log('Construct BC');
  }
}));

หากเราต้องการป้องกันไม่ให้BCมีการเรียกซ้ำA.prototype.initเราอาจจำเป็นต้องละทิ้งรูปแบบของการเรียกตัวสร้างที่สืบทอดมาโดยตรง เราจำเป็นต้องมีการกำหนดทิศทางในระดับหนึ่งเพื่อตรวจสอบว่ามีการโทรซ้ำกันหรือไม่และเกิดการลัดวงจรก่อนที่จะเกิดขึ้น

เราสามารถพิจารณาเปลี่ยนพารามิเตอร์ที่จ่ายให้กับคุณสมบัติการทำงาน: ควบคู่ไปกับprotosการObjectมีข้อมูลดิบอธิบายคุณสมบัติสืบทอดเราอาจจะยังรวมถึงฟังก์ชั่นโปรแกรมสำหรับการเรียกวิธีการเช่นในลักษณะที่ว่าวิธีการปกครองที่เรียกว่ายังเป็น แต่การโทรซ้ำกันจะถูกตรวจพบ และป้องกัน มาดูที่ที่เราสร้างพารามิเตอร์สำหรับpropertiesFn Function:

let makeClass = (name, parents, propertiesFn) => {

  /* ... a bunch of makeClass logic ... */

  // Allows referencing inherited functions; e.g. `parProtos.ParentClass3.usefulMethod`
  let parProtos = {};
  /* ... collect all parent methods in `parProtos` ... */

  // Utility functions for calling inherited methods:
  let util = {};
  util.invokeNoDuplicates = (instance, fnName, args, dups=new Set()) => {

    // Invoke every parent method of name `fnName` first...
    for (let parName of parProtos) {
      if (parProtos[parName].hasOwnProperty(fnName)) {
        // Our parent named `parName` defines the function named `fnName`
        let fn = parProtos[parName][fnName];

        // Check if this function has already been encountered.
        // This solves our duplicate-invocation problem!!
        if (dups.has(fn)) continue;
        dups.add(fn);

        // This is the first time this Function has been encountered.
        // Call it on `instance`, with the desired args. Make sure we
        // include `dups`, so that if the parent method invokes further
        // inherited methods we don't lose track of what functions have
        // have already been called.
        fn.call(instance, ...args, dups);
      }
    }

  };

  // Now we can call `propertiesFn` with an additional `util` param:
  // Resolve `properties` as the result of calling `propertiesFn`:
  let properties = propertiesFn(parProtos, util, Class);

  /* ... a bunch more makeClass logic ... */

};

วัตถุประสงค์ทั้งของการเปลี่ยนแปลงดังกล่าวข้างต้นจะmakeClassเป็นเพื่อให้เรามีอาร์กิวเมนต์เพิ่มเติมจ่ายให้กับเราเมื่อเราเรียกpropertiesFn makeClassนอกจากนี้เราควรทราบด้วยว่าทุกฟังก์ชันที่กำหนดในคลาสใด ๆ ในขณะนี้อาจได้รับพารามิเตอร์หลังจากฟังก์ชันอื่น ๆ ทั้งหมดซึ่งตั้งชื่อdupซึ่งเป็นSetฟังก์ชันที่เก็บฟังก์ชันทั้งหมดที่ถูกเรียกไปแล้วอันเป็นผลมาจากการเรียกใช้เมธอดที่สืบทอดมา:

let A = makeClass('A', {}, () => ({
  init: function() {
    console.log('Construct A');
  }
}));
let B = makeClass('B', { A }, (protos, util) => ({
  init: function(dups) {
    util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups);
    console.log('Construct B');
  }
}));
let C = makeClass('C', { A }, (protos, util) => ({
  init: function(dups) {
    util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups);
    console.log('Construct C');
  }
}));
let BC = makeClass('BC', { B, C }, (protos, util) => ({
  init: function(dups) {
    util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups);
    console.log('Construct BC');
  }
}));

รูปแบบใหม่นี้ประสบความสำเร็จในการทำให้มั่นใจว่า"Construct A"จะถูกบันทึกเพียงครั้งเดียวเมื่อBCเริ่มต้นอินสแตนซ์ แต่มีข้อเสียสามประการซึ่งประการที่สามสำคัญมาก :

  1. รหัสนี้อ่านและบำรุงรักษาได้น้อยลง ความซับซ้อนมากมายซ่อนอยู่เบื้องหลังutil.invokeNoDuplicatesฟังก์ชันนี้และการคิดว่ารูปแบบนี้จะหลีกเลี่ยงการเรียกหลายครั้งได้อย่างไรนั้นไม่ใช่เรื่องง่ายและทำให้ปวดหัว นอกจากนี้เรายังมีdupsพารามิเตอร์ที่น่ารำคาญซึ่งจำเป็นต้องกำหนดไว้ในทุกฟังก์ชันในคลาสทุกฟังก์ชั่นเดียวในชั้นเรียนอุ๊ยตาย
  2. รหัสนี้ช้ากว่า - ต้องใช้ทิศทางและการคำนวณเพิ่มขึ้นเล็กน้อยเพื่อให้ได้ผลลัพธ์ที่ต้องการด้วยการสืบทอดหลายรายการ แต่น่าเสียดายที่นี้มีแนวโน้มที่จะเป็นกรณีที่มีใด ๆวิธีการแก้ปัญหาหลายภาวนาของเรา
  3. ส่วนใหญ่อย่างมีนัยสำคัญโครงสร้างของฟังก์ชั่นที่พึ่งพามรดกได้กลายเป็นที่เข้มงวดมาก ถ้าชั้นย่อยNiftyClassแทนที่ฟังก์ชั่นniftyFunctionและการใช้util.invokeNoDuplicates(this, 'niftyFunction', ...)เพื่อให้ทำงานได้โดยไม่ซ้ำกัน-ภาวนาNiftyClass.prototype.niftyFunctionจะเรียกฟังก์ชั่นที่มีชื่อniftyFunctionของผู้ปกครองชั้นเรียนทุกคนที่กำหนดมันไม่สนใจค่าผลตอบแทนใด ๆ NiftyClass.prototype.niftyFunctionจากชั้นเรียนเหล่านั้นและในที่สุดก็ดำเนินการตรรกะของความเชี่ยวชาญ นี้เป็นเพียงโครงสร้างที่เป็นไปได้ หากNiftyClassสืบทอดCoolClassและGoodClassและคลาสพาเรนต์ทั้งสองนี้ให้niftyFunctionคำจำกัดความของตนเองNiftyClass.prototype.niftyFunctionจะไม่ (โดยไม่เสี่ยงต่อการเรียกใช้หลายครั้ง) จะสามารถ:
    • A.เรียกใช้ตรรกะเฉพาะของNiftyClassอันดับแรกจากนั้นจึงใช้ตรรกะเฉพาะของคลาสพาเรนต์
    • B.เรียกใช้ลอจิกเฉพาะNiftyClassที่จุดใดก็ได้นอกเหนือจากหลังจากที่ลอจิกหลักเฉพาะทั้งหมดเสร็จสิ้นแล้ว
    • C. ปฏิบัติตามเงื่อนไขโดยขึ้นอยู่กับค่าตอบแทนของตรรกะเฉพาะของผู้ปกครอง
    • D.หลีกเลี่ยงการทำงานโดยเฉพาะอย่างยิ่งผู้ปกครองที่เชี่ยวชาญniftyFunctionโดยสิ้นเชิง

แน่นอนว่าเราสามารถแก้ปัญหาตัวอักษรข้างต้นได้โดยการกำหนดฟังก์ชันพิเศษภายใต้util:

  • ก.กำหนดutil.invokeNoDuplicatesSubClassLogicFirst(instance, fnName, ...)
  • B.กำหนดutil.invokeNoDuplicatesSubClassAfterParent(parentName, instance, fnName, ...)( parentNameชื่อของผู้ปกครองที่มีตรรกะเฉพาะจะตามมาทันทีด้วยตรรกะเฉพาะของคลาสเด็ก)
  • C. กำหนดutil.invokeNoDuplicatesCanShortCircuitOnParent(parentName, testFn, instance, fnName, ...)(ในกรณีนี้testFnจะได้รับผลลัพธ์ของตรรกะเฉพาะสำหรับผู้ปกครองที่ตั้งชื่อparentNameและจะส่งคืนtrue/falseค่าที่ระบุว่าควรเกิดการลัดวงจรหรือไม่)
  • D.กำหนดutil.invokeNoDuplicatesBlackListedParents(blackList, instance, fnName, ...)(ในกรณีนี้blackListจะเป็นArrayชื่อแม่ที่ควรข้ามตรรกะเฉพาะไปเลย)

วิธีแก้ปัญหาเหล่านี้พร้อมใช้งานทั้งหมดแต่นี่คือการทำร้ายร่างกายทั้งหมด ! utilสำหรับโครงสร้างที่ไม่ซ้ำกันทุกคนที่มีสายเรียกฟังก์ชั่นได้รับมรดกสามารถใช้เวลาที่เราจะต้องมีวิธีการเฉพาะที่กำหนดไว้ภายใต้ ช่างเป็นหายนะอย่างแท้จริง

ด้วยเหตุนี้เราจึงสามารถเริ่มเห็นความท้าทายของการใช้มรดกหลายอย่างที่ดี การใช้งานเต็มรูปแบบของmakeClassฉันให้ไว้ในคำตอบนี้ไม่ได้พิจารณาถึงปัญหาการเรียกใช้งานหลายครั้งหรือปัญหาอื่น ๆ อีกมากมายที่เกิดขึ้นเกี่ยวกับการสืบทอดหลายรายการ

คำตอบนี้เริ่มยาวมาก ฉันหวังว่าการmakeClassใช้งานที่ฉันรวมไว้จะยังคงมีประโยชน์แม้ว่าจะไม่สมบูรณ์แบบก็ตาม ฉันหวังว่าทุกคนที่สนใจในหัวข้อนี้จะได้รับบริบทเพิ่มเติมเพื่อให้ทราบในขณะที่พวกเขาอ่านต่อไป!


0

ดูแพ็คเกจIeUnit IeUnit

การผสมผสานแนวคิดที่นำมาใช้ใน IeUnit ดูเหมือนจะนำเสนอสิ่งที่คุณกำลังมองหาในรูปแบบที่ไม่หยุดนิ่ง


0

นี่คือตัวอย่างของการเชื่อมโยงต้นแบบโดยใช้ฟังก์ชันตัวสร้าง :

function Lifeform () {             // 1st Constructor function
    this.isLifeform = true;
}

function Animal () {               // 2nd Constructor function
    this.isAnimal = true;
}
Animal.prototype = new Lifeform(); // Animal is a lifeform

function Mammal () {               // 3rd Constructor function
    this.isMammal = true;
}
Mammal.prototype = new Animal();   // Mammal is an animal

function Cat (species) {           // 4th Constructor function
    this.isCat = true;
    this.species = species
}
Cat.prototype = new Mammal();     // Cat is a mammal

แนวคิดนี้ใช้คำจำกัดความของ"คลาส"ของ Yehuda Katz สำหรับ JavaScript:

... "คลาส" ของ JavaScript เป็นเพียงวัตถุฟังก์ชันที่ทำหน้าที่เป็นตัวสร้างบวกกับวัตถุต้นแบบที่แนบมา ( ที่มา: คุรุแคทซ์ )

ซึ่งแตกต่างจากวิธี Object.createเมื่อคลาสถูกสร้างด้วยวิธีนี้และเราต้องการสร้างอินสแตนซ์ของ "คลาส" เราไม่จำเป็นต้องรู้ว่า "คลาส" แต่ละตัวสืบทอดมาจากอะไร เราก็ใช้new.

// Make an instance object of the Cat "Class"
var tiger = new Cat("tiger");

console.log(tiger.isCat, tiger.isMammal, tiger.isAnimal, tiger.isLifeform);
// Outputs: true true true true

ลำดับของการมีอำนาจเหนือกว่าควรเข้าท่า ขั้นแรกให้ดูในวัตถุอินสแตนซ์จากนั้นเป็นต้นแบบจากนั้นเป็นต้นแบบถัดไปเป็นต้น

// Let's say we have another instance, a special alien cat
var alienCat = new Cat("alien");
// We can define a property for the instance object and that will take 
// precendence over the value in the Mammal class (down the chain)
alienCat.isMammal = false;
// OR maybe all cats are mutated to be non-mammals
Cat.prototype.isMammal = false;
console.log(alienCat);

นอกจากนี้เรายังสามารถปรับเปลี่ยนต้นแบบซึ่งจะมีผลกับวัตถุทั้งหมดที่สร้างในคลาส

// All cats are mutated to be non-mammals
Cat.prototype.isMammal = false;
console.log(tiger, alienCat);

เดิมฉันเขียนบางส่วนด้วยคำตอบนี้


2
OP กำลังขอโซ่ต้นแบบหลายตัว (เช่นchildสืบทอดจากparent1และparent2) ตัวอย่างของคุณพูดถึงโซ่เส้นเดียว
poshest

0

มาที่หลังในฉากเป็นSimpleDeclare อย่างไรก็ตามเมื่อต้องจัดการกับการสืบทอดหลายรายการคุณจะยังคงได้รับสำเนาของผู้สร้างดั้งเดิม นั่นเป็นสิ่งจำเป็นใน Javascript ...

Merc


นั่นเป็นสิ่งจำเป็นใน Javascript ... จนถึง ES6 Proxies
Jonathon

พร็อกซีน่าสนใจ! ฉันจะตรวจสอบการเปลี่ยนแปลง SimpleDeclare อย่างแน่นอนเพื่อที่จะไม่ต้องคัดลอกวิธีการโดยใช้พร็อกซีเมื่อพวกเขากลายเป็นส่วนหนึ่งของมาตรฐาน รหัสของ SimpleDeclare นั้นอ่านและเปลี่ยนแปลงได้ง่ายมาก ...
Merc

0

ผมจะใช้ds.oop คล้ายกับ Prototype.js และอื่น ๆ ทำให้การสืบทอดหลาย ๆ อย่างง่ายมากและเรียบง่าย (เพียง 2 หรือ 3 kb) นอกจากนี้ยังรองรับคุณสมบัติที่เป็นระเบียบอื่น ๆ เช่นอินเทอร์เฟซและการฉีดแบบพึ่งพา

/*** multiple inheritance example ***********************************/

var Runner = ds.class({
    run: function() { console.log('I am running...'); }
});

var Walker = ds.class({
    walk: function() { console.log('I am walking...'); }
});

var Person = ds.class({
    inherits: [Runner, Walker],
    eat: function() { console.log('I am eating...'); }
});

var person = new Person();

person.run();
person.walk();
person.eat();

0

เกี่ยวกับสิ่งนี้มันใช้การสืบทอดหลายรายการใน JavaScript:

    class Car {
        constructor(brand) {
            this.carname = brand;
        }
        show() {
            return 'I have a ' + this.carname;
        }
    }

    class Asset {
        constructor(price) {
            this.price = price;
        }
        show() {
            return 'its estimated price is ' + this.price;
        }
    }

    class Model_i1 {        // extends Car and Asset (just a comment for ourselves)
        //
        constructor(brand, price, usefulness) {
            specialize_with(this, new Car(brand));
            specialize_with(this, new Asset(price));
            this.usefulness = usefulness;
        }
        show() {
            return Car.prototype.show.call(this) + ", " + Asset.prototype.show.call(this) + ", Model_i1";
        }
    }

    mycar = new Model_i1("Ford Mustang", "$100K", 16);
    document.getElementById("demo").innerHTML = mycar.show();

และนี่คือรหัสสำหรับฟังก์ชันยูทิลิตี้ specialize_with ():

function specialize_with(o, S) { for (var prop in S) { o[prop] = S[prop]; } }

นี่คือรหัสจริงที่รัน คุณสามารถคัดลอก - วางในไฟล์ html และลองด้วยตัวเอง มันทำงาน

นั่นคือความพยายามในการนำ MI ไปใช้ใน JavaScript มีโค้ดไม่มาก แต่เป็นความรู้เพิ่มเติม

โปรดอ่านบทความทั้งหมดของฉันเกี่ยวกับเรื่องนี้https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS


0

ฉันใช้เพื่อกำหนดคลาสที่ฉันต้องการในคุณสมบัติของผู้อื่นและเพิ่มพร็อกซีเพื่อชี้อัตโนมัติไปยังพวกเขาที่ฉันชอบ:

class A {
    constructor()
    {
        this.test = "a test";
    }

    method()
    {
        console.log("in the method");
    }
}

class B {
    constructor()
    {
        this.extends = [new A()];

        return new Proxy(this, {
            get: function(obj, prop) {

                if(prop in obj)
                    return obj[prop];

                let response = obj.extends.find(function (extended) {
                if(prop in extended)
                    return extended[prop];
            });

            return response ? response[prop] : Reflect.get(...arguments);
            },

        })
    }
}

let b = new B();
b.test ;// "a test";
b.method(); // in the method
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.