ES6 คลาสหลายการสืบทอด


134

ฉันได้ทำวิจัยส่วนใหญ่เกี่ยวกับBabelJSและMDN แล้ว (ซึ่งไม่มีข้อมูลเลย) แต่โปรดอย่าลังเลที่จะบอกฉันว่าฉันไม่ได้ระมัดระวังเพียงพอในการค้นหาข้อมูลเพิ่มเติมเกี่ยวกับ ES6 Spec

ฉันสงสัยว่า ES6 รองรับการสืบทอดหลายแบบในลักษณะเดียวกับภาษาพิมพ์เป็ดอื่น ๆ หรือไม่ ตัวอย่างเช่นฉันสามารถทำสิ่งต่างๆเช่น:

class Example extends ClassOne, ClassTwo {
    constructor() {
    }
}

เพื่อขยายหลายชั้นเรียนไปยังชั้นเรียนใหม่? ถ้าเป็นเช่นนั้นล่ามจะชอบวิธีการ / คุณสมบัติจาก ClassTwo มากกว่า ClassOne หรือไม่?


4
สิ่งนี้เป็นไปไม่ได้จริง ๆ กับวิธีการสืบทอดปัจจุบันทำงานใน js สิ่งที่ใกล้เคียงที่สุดที่คุณทำได้คือmixin
qwertymk

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

ฉันอ่านคลาส ES6 ใหม่ไม่ได้เพิ่มฟังก์ชั่นใหม่ใด ๆ พวกเขาเป็นเพียงน้ำตาลทางไวยากรณ์
Oriol


@ โอริออลเป็นน้ำตาลไวยากรณ์ แต่ฉันเคยสงสัยว่าน้ำตาลนั้นทำอะไรกับหลาย ๆ ชั้นภายในหรือไม่
BTC

คำตอบ:


70

วัตถุสามารถมีได้เพียงต้นแบบเดียว การสืบทอดจากสองคลาสสามารถทำได้โดยการสร้างอ็อบเจ็กต์พาเรนต์โดยใช้โปรโตไทป์พาเรนต์สองตัวรวมกัน

ไวยากรณ์สำหรับคลาสย่อยทำให้สามารถทำได้ในการประกาศเนื่องจากด้านขวามือของextendsอนุประโยคสามารถเป็นนิพจน์ใดก็ได้ ดังนั้นคุณสามารถเขียนฟังก์ชันที่รวมต้นแบบตามเกณฑ์ที่คุณต้องการและเรียกใช้ฟังก์ชันนั้นในการประกาศคลาส


1
ฉันสงสัยเสมอว่ามีวิธีใดในการตั้งค่า getter บน__proto__ลิงก์เพื่อส่งต่อการค้นหาเสาไปยังวัตถุที่ถูกต้องหรือไม่? ฉันพยายามแล้วแต่ไม่เคยได้ผล
qwertymk

3
@qwertymk โปรดทราบว่า__proto__คุณลักษณะนี้เป็นคุณลักษณะที่เลิกใช้แล้ว มันสะท้อนให้เห็นถึงลิงก์ต้นแบบภายใน แต่ไม่ใช่ลิงก์ต้นแบบภายในจริงๆ
Pointy

ดังนั้นจึงไม่เคยมีโอกาสที่จะแฮ็คแบบนั้นได้เลย? core-js ทำสิ่งที่คล้ายกันกับการสนับสนุนจุดอ่อนโดยใช้ getters การสืบทอดหลาย ๆ อย่างจะดีมาก
qwertymk

1
@qwertymk ดีฉันไม่สามารถพูดด้วยอำนาจว่ามันเป็นไปไม่ได้แน่นอน โดยส่วนตัวแล้วฉันใช้การสืบทอดใน JavaScript มากน้อยครั้งมาก ในความเป็นจริงฉันใช้ต้นแบบค่อนข้างน้อยสำหรับเรื่องนั้น
Pointy

2
นี่คือวิธีการที่ฉันมาด้วย: esdiscuss.org/topic/symbol-for-modifying-property-lookup ตัวอย่าง: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. วิธีการและคุณสมบัติของคอนสตรัคเตอร์สุดท้ายที่ส่งผ่านไปnew MultiClassมีลำดับความสำคัญสูงสุดมันเป็นเพียงการผสมลงในต้นแบบใหม่ ฉันคิดว่ามีทางออกที่ดีกว่านี้หากนำมาใช้ใหม่โดยใช้ ES6 Proxies แต่ยังไม่มีการสนับสนุนดั้งเดิมเพียงพอสำหรับมัน
trusktr

89

ตรวจสอบตัวอย่างของฉันด้านล่างsuperวิธีการทำงานตามที่คาดไว้ การใช้เทคนิคเล็กน้อยก็instanceofใช้ได้ผล (โดยส่วนใหญ่):

// base class
class A {  
  foo() {
    console.log(`from A -> inside instance of A: ${this instanceof A}`);
  }
}

// B mixin, will need a wrapper over it to be used
const B = (B) => class extends B {
  foo() {
    if (super.foo) super.foo(); // mixins don't know who is super, guard against not having the method
    console.log(`from B -> inside instance of B: ${this instanceof B}`);
  }
};

// C mixin, will need a wrapper over it to be used
const C = (C) => class extends C {
  foo() {
    if (super.foo) super.foo(); // mixins don't know who is super, guard against not having the method
    console.log(`from C -> inside instance of C: ${this instanceof C}`);
  }
};

// D class, extends A, B and C, preserving composition and super method
class D extends C(B(A)) {  
  foo() {
    super.foo();
    console.log(`from D -> inside instance of D: ${this instanceof D}`);
  }
}

// E class, extends A and C
class E extends C(A) {
  foo() {
    super.foo();
    console.log(`from E -> inside instance of E: ${this instanceof E}`);
  }
}

// F class, extends B only
class F extends B(Object) {
  foo() {
    super.foo();
    console.log(`from F -> inside instance of F: ${this instanceof F}`);
  }
}

// G class, C wrap to be used with new decorator, pretty format
class G extends C(Object) {}

const inst1 = new D(),
      inst2 = new E(),
      inst3 = new F(),
      inst4 = new G(),
      inst5 = new (B(Object)); // instance only B, ugly format

console.log(`Test D: extends A, B, C -> outside instance of D: ${inst1 instanceof D}`);
inst1.foo();
console.log('-');
console.log(`Test E: extends A, C -> outside instance of E: ${inst2 instanceof E}`);
inst2.foo();
console.log('-');
console.log(`Test F: extends B -> outside instance of F: ${inst3 instanceof F}`);
inst3.foo();
console.log('-');
console.log(`Test G: wraper to use C alone with "new" decorator, pretty format -> outside instance of G: ${inst4 instanceof G}`);
inst4.foo();
console.log('-');
console.log(`Test B alone, ugly format "new (B(Object))" -> outside instance of B: ${inst5 instanceof B}, this one fails`);
inst5.foo();

จะพิมพ์ออกมา

การทดสอบ D: ขยาย A, B, C -> อินสแตนซ์ภายนอกของ D: true
จาก A -> อินสแตนซ์ภายในของ A: true
จาก B -> อินสแตนซ์ภายในของ B: จริง
จาก C -> อินสแตนซ์ภายในของ C: true
จาก D -> อินสแตนซ์ภายในของ D: true
-
การทดสอบ E: ขยาย A, C -> นอกอินสแตนซ์ของ E: จริง
จาก A -> อินสแตนซ์ภายในของ A: true
จาก C -> อินสแตนซ์ภายในของ C: true
จาก E -> อินสแตนซ์ภายในของ E: true
-
ทดสอบ F: ขยาย B -> นอกอินสแตนซ์ของ F: true
จาก B -> อินสแตนซ์ภายในของ B: จริง
จาก F -> อินสแตนซ์ภายในของ F: true
-
ทดสอบ G: wraper เพื่อใช้ C เพียงอย่างเดียวกับมัณฑนากร "ใหม่" รูปแบบที่สวยงาม -> อินสแตนซ์ภายนอกของ G: true
จาก C -> อินสแตนซ์ภายในของ C: true
-
ทดสอบ B เพียงอย่างเดียวรูปแบบที่น่าเกลียด "ใหม่ (B (วัตถุ))" -> ภายนอกอินสแตนซ์ของ B: เท็จอันนี้ล้มเหลว
จาก B -> อินสแตนซ์ภายในของ B: จริง

เชื่อมโยงไปยังซอรอบ ๆ


1
คุณสามารถแก้ไขปัญหาที่รูปแบบ "น่าเกลียด" ของ B (Object) (B||Object)โดยการขยาย
Aaron

@Aaron ฉันไม่แน่ใจจริงๆว่าฉันติดตามคุณเรื่องนี้ (หรือคุณกำลังติดตามฉัน) หากF extends (B||Object)เป็นF extends B(Object)เช่นนั้นมันจะขยาย B mixin เหมือนเดิม (เป็นฟังก์ชัน) ดังนั้น F จะขยายต้นแบบฟังก์ชันเริ่มต้นเท่านั้นเนื่องจาก B ไม่เคยดำเนินการ โดยการใช้F extends B(Object)เรากำลังเรียกใช้ฟังก์ชัน B และ F จะขยาย 'อะไรก็ได้' ฟังก์ชัน B ที่ส่งกลับในกรณีนี้เป็นคลาส B ที่กำหนดไว้ในฟังก์ชัน B ... แฮ็คขนาดเล็กเพื่อให้การตั้งชื่อคลาสถูกต้อง
Poelinca Dorin

@Aaron สิ่งที่เราทำได้คือใช้พารามิเตอร์เริ่มต้นของฟังก์ชันconst B = (B = Object) => class extends B {แล้วใช้class F extends B() {สำหรับการใช้งานที่สวยกว่า แต่แฮ็ค Kappa ที่น่า
เกลียด

const B = (B) => class extends (B||Object) {จะให้คุณแทนที่inst5 = new (B(Object)); // instance only B, ugly formatด้วยinst5 = new (B());หรือบางทีฉันอาจเข้าใจผิดบริบท ...
แอรอน

@Aaron ใช่ว่าจะทำงานได้ดีจนกว่าconsole.log('from B -> inside instance of B: ${this instanceof B}');แม่มดจะล้มเหลวเช่นRight-hand side of 'instanceof' is not an objectกัน การใช้const B = (B = Object) => class extends B {ตามที่กล่าวไว้ก่อนหน้านี้จะผ่านการทดสอบอินสแตนซ์และให้การinst5 = new (B());ใช้งานแก่คุณเช่นกันหากคุณต้องการ
Poelinca Dorin

23

การใช้งานของ Sergio Carneiro และ Jonต้องการให้คุณกำหนดฟังก์ชัน initializer สำหรับทุกคลาสยกเว้นคลาสเดียว นี่คือฟังก์ชันการรวมรุ่นที่แก้ไขแล้วซึ่งใช้พารามิเตอร์เริ่มต้นในตัวสร้างแทน รวมถึงความคิดเห็นบางส่วนของฉันด้วย

var aggregation = (baseClass, ...mixins) => {
    class base extends baseClass {
        constructor (...args) {
            super(...args);
            mixins.forEach((mixin) => {
                copyProps(this,(new mixin));
            });
        }
    }
    let copyProps = (target, source) => {  // this function copies all properties and symbols, filtering out some special ones
        Object.getOwnPropertyNames(source)
              .concat(Object.getOwnPropertySymbols(source))
              .forEach((prop) => {
                 if (!prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                    Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
               })
    }
    mixins.forEach((mixin) => { // outside contructor() to allow aggregation(A,B,C).staticFunction() to be called etc.
        copyProps(base.prototype, mixin.prototype);
        copyProps(base, mixin);
    });
    return base;
}

นี่คือการสาธิตเล็กน้อย:

class Person{
   constructor(n){
      this.name=n;
   }
}
class Male{
   constructor(s='male'){
      this.sex=s;
   }
}
class Child{
   constructor(a=12){
      this.age=a;
   }
   tellAge(){console.log(this.name+' is '+this.age+' years old.');}
}
class Boy extends aggregation(Person,Male,Child){}
var m = new Boy('Mike');
m.tellAge(); // Mike is 12 years old.

ฟังก์ชันการรวมนี้จะชอบคุณสมบัติและวิธีการของคลาสที่ปรากฏในรายการคลาสในภายหลัง


3
เมื่อฉันลองใช้สิ่งนี้กับการตอบสนองComponentมันไม่ได้ผล เพียงแค่ FYI สำหรับคนอื่นที่อาจต้องการเพื่อจุดประสงค์นี้
r3wt

ซึ่งเขียนทับตัวแปรและฟังก์ชันที่มีชื่อเดียวกัน
Vincent Hoch-Drei

17

Justin Fagnani อธิบายวิธีที่สะอาดมาก (imho) ในการเขียนหลายชั้นเรียนให้เป็นหนึ่งเดียวโดยใช้ข้อเท็จจริงที่ว่าใน ES2015 สามารถสร้างคลาสด้วยนิพจน์ของคลาสได้

นิพจน์เทียบกับการประกาศ

โดยพื้นฐานแล้วเช่นเดียวกับที่คุณสามารถสร้างฟังก์ชันด้วยนิพจน์:

function myFunction() {}      // function declaration
var myFunction = function(){} // function expression

คุณสามารถทำเช่นเดียวกันกับชั้นเรียน:

class MyClass {}             // class declaration
var MyClass = class {}       // class expression

นิพจน์จะได้รับการประเมินที่รันไทม์เมื่อโค้ดทำงานในขณะที่การประกาศจะดำเนินการล่วงหน้า

การใช้นิพจน์คลาสเพื่อสร้างมิกซ์อิน

คุณสามารถใช้สิ่งนี้เพื่อสร้างฟังก์ชันที่สร้างคลาสแบบไดนามิกเฉพาะเมื่อฟังก์ชันถูกเรียกใช้:

function createClassExtending(superclass) {
  return class AwesomeClass extends superclass {
    // you class body here as usual
  }
}

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

class A {}
class B {}
var ExtendingA = createClassExtending(A)
var ExtendingB = createClassExtending(B)

หากคุณต้องการผสมหลายคลาสเข้าด้วยกันเนื่องจากคลาส ES6 รองรับเฉพาะการสืบทอดเดียวคุณต้องสร้างชุดคลาสที่มีคลาสทั้งหมดที่คุณต้องการผสมเข้าด้วยกัน สมมติว่าคุณต้องการสร้างคลาส C ที่ขยายทั้ง A และ B คุณสามารถทำได้:

class A {}
class B extends A {}
class C extends B {}  // C extends both A and B

ปัญหาของเรื่องนี้คือมันคงที่มาก หากคุณตัดสินใจในภายหลังว่าต้องการสร้างคลาส D ที่ขยาย B แต่ไม่ใช่ A แสดงว่าคุณมีปัญหา

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

class Base {} // some base class to keep the arrow functions simple
var A = (superclass) => class A extends superclass
var B = (superclass) => class B extends superclass
var C = B(A(Base))
var D = B(Base)

สังเกตว่าเราตัดสินใจเฉพาะในช่วงสุดท้ายว่าจะรวมคลาสใดไว้ในลำดับชั้น


8

สิ่งนี้เป็นไปไม่ได้จริง ๆ กับวิธีการสืบทอดต้นแบบทำงาน มาดูกันว่าอุปกรณ์ประกอบฉากที่สืบทอดมาทำงานอย่างไรใน js

var parent = {a: function() { console.log('ay'); }};
var child = Object.create(parent);
child.a() // first look in child instance, nope let's go to it's prototype
          // then look in parent, found! return the method

มาดูกันว่าจะเกิดอะไรขึ้นเมื่อคุณเข้าถึงเสาที่ไม่มีอยู่:

child.b; // first look in child instance, nope let's go to it's prototype
         // then look in parent, nope let's go to it's prototype
         // then look in Object.prototype, nope let's go to it's prototype
         // then look at null, give up and return undefined

คุณสามารถใช้มิกซ์อินเพื่อรับฟังก์ชั่นบางอย่าง แต่คุณจะไม่ได้รับการผูกมัดล่าช้า

var a = {x: '1'};
var b = {y: '2'};
var c = createWithMixin([a, b]);
c.x; // 1
c.y; // 2
b.z = 3;
c.z; // undefined

VS

var a = {x: 1}
var o = Object.create(a);
o.x; // 1
a.y = 2;
o.y; // 2

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

2

ฉันคิดวิธีแก้ปัญหาเหล่านี้:

'use strict';

const _         = require( 'lodash' );

module.exports  = function( ParentClass ) {

    if( ! ParentClass ) ParentClass = class {};

    class AbstractClass extends ParentClass {
        /**
         * Constructor
        **/
        constructor( configs, ...args ) {
            if ( new.target === AbstractClass )
                throw new TypeError( "Cannot construct Abstract instances directly" );

            super( args );

            if( this.defaults === undefined )
                throw new TypeError( new.target.name + " must contain 'defaults' getter" );

            this.configs = configs;
        }
        /**
         * Getters / Setters
        **/
        // Getting module configs
        get configs() {
            return this._configs;
        }
        // Setting module configs
        set configs( configs ) {
            if( ! this._configs ) this._configs = _.defaultsDeep( configs, this.defaults );
        }
    }

    return AbstractClass;
}

การใช้งาน:

const EventEmitter  = require( 'events' );
const AbstractClass = require( './abstracts/class' )( EventEmitter );

class MyClass extends AbstractClass {
    get defaults() {
        return {
            works: true,
            minuses: [
                'u can have only 1 class as parent wich was\'t made by u',
                'every othere classes should be your\'s'
            ]
        };
    }
}

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

const EventEmitter  = require( 'events' );
const A = require( './abstracts/a' )(EventEmitter);
const B = require( './abstracts/b' )(A);
const C = require( './abstracts/b' )(B);

ใช้งานได้สำหรับฉันในโหนด v5.4.1 ที่มีแฟล็ก --harmony


ฉันไม่คิดว่าคุณต้องการค่าสถานะความสามัคคีสำหรับโหนด 4x ขึ้นไป
Umayr

2

ใช้ Mixins สำหรับการสืบทอดหลายรายการของ ES6

let classTwo = Base => class extends Base{
    // ClassTwo Code
};

class Example extends classTwo(ClassOne) {
    constructor() {
    }
}

3
การสืบทอดหลาย ๆ อย่างไม่ควรหมายถึงone class inherits from 2 or more unrelated classes? สิ่งที่ตัวอย่างของคุณแสดงคือคลาสหนึ่งที่สืบทอดมาจาก 2 แต่คลาสที่เกี่ยวข้องกัน นี่คือการสืบทอดเดียวไม่ใช่การสืบทอดหลายรายการ
vlad-ardelean

@ vlad-ardelean จริงๆแล้วความสัมพันธ์นั้นเป็นของเทียมกล่าวคือ classTwoจัดตั้งขึ้นแบบไดนามิกโดยโทร JS ไม่มีการสืบทอดโครงสร้าง ฉันไม่สามารถมองเห็นสถานการณ์ JS ที่มิกซ์อินทำงานแตกต่างจากที่คุณคาดหวังว่าจะกำหนดคอนเซ็ปต์เป็น MI จากโลก OO ที่แท้จริง (นอกเหนือจาก 'super'-chain ที่กำหนดไว้) บางทีคนที่มีความรู้มากกว่าฉันก็สามารถจัดหาให้ได้
ถล่ม

@collapsar ฉันคิดว่าคุณพูดถูกจริงๆ JS มีการสืบทอดต้นแบบซึ่งหมายความว่ามีโซ่ต้นแบบที่แต่ละต้นแบบในห่วงโซ่มีแม่เดียว เมื่อผสมในกลุ่มคลาสทั้งหมดลงในห่วงโซ่ต้นแบบตามลำดับที่กำหนดไว้จะมีประสิทธิภาพเหมือนกับ MI ในโลก OO
Stijn de Witt

2

จากหน้าes6-features.org/#ClassInheritanceFromExpressionsคุณสามารถเขียนฟังก์ชันการรวมเพื่ออนุญาตการสืบทอดหลายรายการ:

คลาส Rectangle ขยายการรวม (Shape, Colored, ZCoord) {}

var aggregation = (baseClass, ...mixins) => {
    let base = class _Combined extends baseClass {
        constructor (...args) {
            super(...args)
            mixins.forEach((mixin) => {
                mixin.prototype.initializer.call(this)
            })
        }
    }
    let copyProps = (target, source) => {
        Object.getOwnPropertyNames(source)
            .concat(Object.getOwnPropertySymbols(source))
            .forEach((prop) => {
            if (prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                return
            Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop))
        })
    }
    mixins.forEach((mixin) => {
        copyProps(base.prototype, mixin.prototype)
        copyProps(base, mixin)
    })
    return base
}

แต่ที่ให้ไว้แล้วในห้องสมุดเช่น การรวมตัว


1

Well Object.assignช่วยให้คุณสามารถทำสิ่งที่ใกล้เคียงได้แม้ว่าจะมีการจัดองค์ประกอบด้วยคลาส ES6 มากกว่าเล็กน้อย

class Animal {
    constructor(){ 
     Object.assign(this, new Shark()) 
     Object.assign(this, new Clock()) 
  }
}

class Shark {
  // only what's in constructor will be on the object, ence the weird this.bite = this.bite.
  constructor(){ this.color = "black"; this.bite = this.bite }
  bite(){ console.log("bite") }
  eat(){ console.log('eat') }
}

class Clock{
  constructor(){ this.tick = this.tick; }
  tick(){ console.log("tick"); }
}

let animal = new Animal();
animal.bite();
console.log(animal.color);
animal.tick();

ฉันไม่เคยเห็นสิ่งนี้ใช้ที่ไหนเลย แต่มันมีประโยชน์มากจริงๆ คุณสามารถใช้function shark(){}แทนคลาสได้ แต่มีข้อดีของการใช้คลาสแทน

ฉันเชื่อว่าสิ่งเดียวที่แตกต่างจากการสืบทอดด้วยextendคีย์เวิร์ดคือฟังก์ชันไม่ได้อยู่เฉพาะในprototypeอ็อบเจ็กต์เท่านั้น

ดังนั้นตอนนี้เมื่อคุณไม่สร้างขึ้นมีวิธีการในขณะที่เพียงต้นแบบของมันมีวิธีการnew Shark()sharkbiteeat


วิธีนี้ใช้ไม่ได้ วิธีการต้นแบบจะไม่ถูกผสมเข้าด้วยกันและการผูกจะไม่ถูกต้อง
jonschlinkert

1

ไม่มีวิธีง่ายๆในการสืบทอดคลาสหลาย ๆ ฉันติดตามการรวมกันของการเชื่อมโยงและการสืบทอดเพื่อให้เกิดพฤติกรรมแบบนี้

    class Person {
        constructor(firstname, lastname, age){
            this.firstname = firstname,
            this.lastname = lastname
            this.Age = age
        }

        fullname(){
                return this.firstname +" " + this.lastname;
            } 
    }

    class Organization {
        constructor(orgname){
            this.orgname = orgname;
        }
    }

    class Employee extends Person{
        constructor(firstname, lastname, age,id) {
            super(firstname, lastname, age);
            this.id = id;
        }

    }
    var emp = new Employee("John", "Doe", 33,12345);
    Object.assign(emp, new Organization("Innovate"));
    console.log(emp.id);
    console.log(emp.orgname);
    console.log(emp.fullname());

หวังว่านี่จะเป็นประโยชน์


1

โซลูชัน ES6 นี้ใช้ได้ผลสำหรับฉัน:

หลาย inheritance.js

export function allOf(BaseClass, ...Mixins) {

  function copyProperties(target, source) {
    const allPropertyNames = Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source))

    allPropertyNames.forEach((propertyName) => {
      if (propertyName.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
        return
      Object.defineProperty(target, propertyName, Object.getOwnPropertyDescriptor(source, propertyName))
    })
  }

  class Base extends BaseClass
  {
    constructor (...args) {
      super(...args)

      Mixins.forEach((Mixin) => {
        copyProperties(this, new Mixin(...args))
      })
    }
  }

  Mixins.forEach((mixin) => {
    copyProperties(Base.prototype, Mixin.prototype)
  })

  return Base
}

main.js

import { allOf } from "./multiple-inheritance.js"

class A
{
    constructor(name) {
        this.name = name
    }
    sayA() {
        return this.name
    }
}

class B
{
    constructor(name) {
        this.name = name
    }
    sayB() {
        return this.name
    }
}

class AB extends allOf(A, B)
{
    sayAB() {
        return this.name
    }
}

const ab = new AB("ab")
console.log("ab.sayA() = "+ab.sayA()+", ab.sayB() = "+ab.sayB()+", ab.sayAB() = "+ab.sayAB())

ผลตอบแทนบนคอนโซลเบราว์เซอร์:

ab.sayA() = ab, ab.sayB() = ab, ab.sayAB() = ab

ES6 คือ JavaScript!
Bergi

1

ฉันใช้เวลาครึ่งสัปดาห์ในการหาข้อมูลนี้ด้วยตัวเองและเขียนบทความทั้งหมดเกี่ยวกับเรื่องนี้https://github.com/latitov/OOP_MI_Ct_oPlus_in_JSและหวังว่ามันจะช่วยคุณได้บ้าง

กล่าวโดยย่อนี่คือวิธีการนำ MI ไปใช้ใน 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]; } }

อีกครั้งโปรดดูที่https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS


1

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

let Join = (...classList) => {

    class AggregatorClass {

        constructor() {
            classList.forEach((classItem, index) => {

                let propNames = Object.getOwnPropertyNames(classItem.prototype);

                propNames.forEach(name => {
                    if (name !== 'constructor') {
                        AggregatorClass.prototype[name] = classItem.prototype[name];
                    }
                });
            });

            classList.forEach(constructor => {
                Object.assign(AggregatorClass.prototype, new constructor())
            });
        }
    }


    return AggregatorClass

};

1

คำตอบของฉันดูเหมือนรหัสน้อยกว่าและใช้ได้กับฉัน:

class Nose {
  constructor() {
    this.booger = 'ready'; 
  }

  pick() {
    console.log('pick your nose')
  } 
}

class Ear {
  constructor() {
    this.wax = 'ready'; 
  }

  dig() {
    console.log('dig in your ear')
  } 
}

class Gross extends Classes([Nose,Ear]) {
  constructor() {
    super();
    this.gross = true;
  }
}

function Classes(bases) {
  class Bases {
    constructor() {
      bases.forEach(base => Object.assign(this, new base()));
    }
  }
  bases.forEach(base => {
    base.prototype
    .properties()
    .filter(prop => prop != 'constructor')
    .forEach(prop => Bases.prototype[prop] = base.prototype[prop])
  })
  return Bases;
}


// test it
function dontLook() {
  var grossMan = new Gross();
  grossMan.pick(); // eww
  grossMan.dig();  // yuck!
}

0

ใช้ขอบเขตกับฟังก์ชันที่กำหนดเองเพื่อจัดการการสืบทอดหลายรายการด้วย es6

var aggregation = (baseClass, ...mixins) => {
    let base = class _Combined extends baseClass {
        constructor (...args) {
            super(...args)
            mixins.forEach((mixin) => {
                mixin.prototype.initializer.call(this)
            })
        }
    }
    let copyProps = (target, source) => {
        Object.getOwnPropertyNames(source)
            .concat(Object.getOwnPropertySymbols(source))
            .forEach((prop) => {
            if (prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                return
            Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop))
        })
    }
    mixins.forEach((mixin) => {
        copyProps(base.prototype, mixin.prototype)
        copyProps(base, mixin)
    })
    return base
}

class Colored {
    initializer ()     { this._color = "white" }
    get color ()       { return this._color }
    set color (v)      { this._color = v }
}

class ZCoord {
    initializer ()     { this._z = 0 }
    get z ()           { return this._z }
    set z (v)          { this._z = v }
}

class Shape {
    constructor (x, y) { this._x = x; this._y = y }
    get x ()           { return this._x }
    set x (v)          { this._x = v }
    get y ()           { return this._y }
    set y (v)          { this._y = v }
}

class Rectangle extends aggregation(Shape, Colored, ZCoord) {}

var rect = new Rectangle(7, 42)
rect.z     = 1000
rect.color = "red"
console.log(rect.x, rect.y, rect.z, rect.color)


0

ฉันจะเพิ่มโซลูชันของฉันด้วย - ฉันพบว่ามันเป็นมิตรกับตัวเองที่สุดจากสิ่งที่ฉันอ่านในหัวข้อนี้

export const aggregate = (...mixins) => (Base) => {
  const copyProps = (target, source) => {
    Object.getOwnPropertyNames(source)
      .concat(Object.getOwnPropertySymbols(source))
      .forEach((prop) => {
        if (prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/)) {
          return;
        }
        Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
      });
  };
  mixins.forEach((mixin) => {
    copyProps(Base, mixin);
    copyProps(Base.prototype, mixin.prototype);
  });
  return Base;
};

คุณสามารถใช้งานได้ดังนี้:

class _MyBaseClass {}
const MyBaseClass = aggregate(ExtensionOne, ExtensionTwo)(_MyBaseClass);

0

เพื่อเป็นการพิสูจน์แนวคิดฉันทำหน้าที่ต่อไปนี้ ใช้รายชื่อคลาสและรวบรวมเป็นคลาสใหม่ (ต้นแบบสุดท้ายชนะจึงไม่มีข้อขัดแย้ง) เมื่อสร้างฟังก์ชันประกอบผู้ใช้สามารถเลือกใช้ตัวสร้างดั้งเดิมทั้งหมด [ sic! ] หรือผ่านของตัวเอง นี่เป็นความท้าทายที่ยิ่งใหญ่ที่สุดของการทดลองนี้คือการหาคำอธิบายว่าตัวสร้างควรทำอะไร การคัดลอกวิธีการลงในต้นแบบไม่ใช่ปัญหา แต่เป็นตรรกะที่ตั้งใจไว้ของวัตถุที่ประกอบขึ้นใหม่คืออะไร หรือบางทีมันควรจะเป็น constructorless? ใน Python จากสิ่งที่ฉันรู้พบว่าตัวสร้างที่ตรงกันแต่ฟังก์ชันใน JS ได้รับการยอมรับมากกว่าดังนั้นจึงสามารถส่งผ่านไปยังฟังก์ชันได้ทุกอย่างและจากลายเซ็นจะไม่ชัดเจน

ฉันไม่คิดว่ามันเหมาะสมที่สุด แต่จุดประสงค์คือการสำรวจความเป็นไปได้ instanceofจะไม่ทำงานตามที่คาดไว้ซึ่งฉันเดาว่าเป็นคนเกียจคร้านเนื่องจากนักพัฒนาที่มุ่งเน้นชั้นเรียนชอบใช้สิ่งนี้เป็นเครื่องมือ

บางที JavaScript ก็ไม่มี

/*
    (c) Jon Krazov 2019

    Below is an experiment searching boundaries of JavaScript.
    It allows to compute one class out of many classes.

    Usage 1: Without own constructor

    If no constructor is passed then constructor of each class will be called
    with params passed in object. In case of missing params, constructor
    will be called without params.

    Example:

    const MyClass1 = computeClass([Class1, Class2, Class3]);
    const myClass1Instance = new MyClass1({
        'Class1': [1, 2],
        'Class2': ['test'],
        'Class3': [(value) => value],
    });

    Usage 2: With own constructor

    If constructor is passed in options object (second param) then it will
    be called in place of constructors of all classes.

    Example:

    const MyClass2 = computeClass([Class1, Class2, Class3], {
        ownConstructor(param1) {
            this.name = param1;
        }
    });
    const myClass2Instance = new MyClass2('Geoffrey');
*/

// actual function

var computeClass = (classes = [], { ownConstructor = null } = {}) => {
    const noConstructor = (value) => value != 'constructor';

    const ComputedClass = ownConstructor === null
        ? class ComputedClass {
            constructor(args) {
                classes.forEach((Current) => {
                    const params = args[Current.name];

                    if (params) {
                        Object.assign(this, new Current(...params));
                    } else {
                        Object.assign(this, new Current());
                    }
                })
            }
        }
        : class ComputedClass {
            constructor(...args) {
                if (typeof ownConstructor != 'function') {
                    throw Error('ownConstructor has to be a function!');
                }
                ownConstructor.call(this, ...args);
            } 
        };

    const prototype = classes.reduce(
        (composedPrototype, currentClass) => {
            const partialPrototype = Object.getOwnPropertyNames(currentClass.prototype)
                .reduce(
                    (result, propName) =>
                        noConstructor(propName)
                            ? Object.assign(
                                    result,
                                    { [propName]: currentClass.prototype[propName] }
                                )
                            : result,
                    {}
                );

            return Object.assign(composedPrototype, partialPrototype);
        },
        {}
    );

    Object.entries(prototype).forEach(([prop, value]) => {
	Object.defineProperty(ComputedClass.prototype, prop, { value });
    });
    
    return ComputedClass;
}

// demo part

var A = class A {
    constructor(a) {
        this.a = a;
    }
    sayA() { console.log('I am saying A'); }
}

var B = class B {
    constructor(b) {
        this.b = b;
    }
    sayB() { console.log('I am saying B'); }
}

console.log('class A', A);
console.log('class B', B);

var C = computeClass([A, B]);

console.log('Composed class');
console.log('var C = computeClass([A, B]);', C);
console.log('C.prototype', C.prototype);

var c = new C({ A: [2], B: [32] });

console.log('var c = new C({ A: [2], B: [32] })', c);
console.log('c instanceof A', c instanceof A);
console.log('c instanceof B', c instanceof B);

console.log('Now c will say:')
c.sayA();
c.sayB();

console.log('---');

var D = computeClass([A, B], {
    ownConstructor(c) {
        this.c = c;
    }
});

console.log(`var D = computeClass([A, B], {
    ownConstructor(c) {
        this.c = c;
    }
});`);

var d = new D(42);

console.log('var d = new D(42)', d);

console.log('Now d will say:')
d.sayA();
d.sayB();

console.log('---');

var E = computeClass();

console.log('var E = computeClass();', E);

var e = new E();

console.log('var e = new E()', e);

โพสต์ครั้งแรกที่นี่ (gist.github.com)



-3

นี่เป็นวิธีที่ยอดเยี่ยม / เส็งเคร็งจริงๆในการขยายหลายชั้นเรียน ฉันกำลังใช้ฟังก์ชันสองอย่างที่ Babel ใส่ไว้ในโค้ดที่เกิดขึ้นของฉัน ฟังก์ชันจะสร้างคลาสใหม่ที่สืบทอด class1 และ class1 สืบทอด class2 และอื่น ๆ มันมีปัญหา แต่เป็นความคิดที่สนุก

var _typeof = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? function (obj) {
  return typeof obj
} : function (obj) {
  return obj && typeof Symbol === 'function' && obj.constructor === Symbol ? 'symbol' : typeof obj
}

function _inherits (subClass, superClass) {
  if (typeof superClass !== 'function' && superClass !== null) {
    throw new TypeError('Super expression must either be null or a function, not ' + (
      typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)))
  }
  subClass.prototype = Object.create(
    superClass && superClass.prototype,
    {
      constructor: {
        value: subClass,
        enumerable: false,
        writable: true,
        configurable: true
      }
    })
  if (superClass) {
    Object.setPrototypeOf
    ? Object.setPrototypeOf(subClass, superClass)
    : subClass.__proto__ = superClass.__proto__  // eslint-disable-line no-proto
  }
}

function _m (...classes) {
  let NewSuperClass = function () {}
  let c1 = NewSuperClass
  for (let c of classes) {
    _inherits(c1, c)
    c1 = c
  }
  return NewSuperClass
}

import React from 'react'

/**
 * Adds `this.log()` to your component.
 * Log message will be prefixed with the name of the component and the time of the message.
 */
export default class LoggingComponent extends React.Component {
  log (...msgs) {
    if (__DEBUG__) {
      console.log(`[${(new Date()).toLocaleTimeString()}] [${this.constructor.name}]`, ...msgs)
    }
  }
}

export class MyBaseComponent extends _m(LoggingComponent, StupidComponent) {}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.