Pertanyaan ES6 Class Multiple inheritance


Saya telah melakukan sebagian besar penelitian saya tentang ini BabelJS dan terus MDN (yang tidak memiliki informasi sama sekali), tetapi jangan ragu untuk memberi tahu saya jika saya belum cukup berhati-hati dalam mencari informasi lebih lanjut tentang Spesifikasi ES6.

Saya bertanya-tanya apakah ES6 mendukung multiple inheritance dengan cara yang sama seperti yang dilakukan oleh bahasa yang menggunakan bahasa bebek. Misalnya, bisakah saya melakukan sesuatu seperti:

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

untuk memperluas beberapa kelas ke kelas baru? Jika demikian, apakah interpreter lebih menyukai metode / properti dari ClassTwo daripada ClassOne?


76
2018-04-26 15:03


asal


Jawaban:


Sebuah objek hanya dapat memiliki satu prototipe. Mewarisi dari dua kelas dapat dilakukan dengan membuat objek induk sebagai kombinasi dari dua prototipe induk.

Sintaks untuk subclassing memungkinkan untuk melakukan itu dalam deklarasi, karena sisi kanan dari extends klausa dapat berupa ekspresi apa pun. Dengan demikian, Anda dapat menulis fungsi yang menggabungkan prototipe sesuai dengan kriteria apa pun yang Anda suka, dan memanggil fungsi itu dalam deklarasi kelas.


38
2018-04-26 15:11



Lihat contoh saya di bawah ini, super metode bekerja seperti yang diharapkan. Menggunakan beberapa trik bahkan instanceof bekerja (sebagian besar waktu):

// 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();

Akan dicetak

Uji D: memperluas A, B, C -> contoh luar D: benar
dari A -> dalam contoh A: true
dari B -> contoh di dalam B: true
dari C -> contoh di dalam C: true
dari D -> contoh di dalam D: true
-
Test E: memperluas A, C -> contoh luar E: true
dari A -> dalam contoh A: true
dari C -> contoh di dalam C: true
dari E -> di dalam contoh E: true
-
Uji F: memperluas B -> contoh luar F: true
dari B -> contoh di dalam B: true
dari F -> dalam contoh F: true
-
Test G: wraper untuk menggunakan C sendiri dengan dekorator "baru", format cantik -> contoh luar G: true
dari C -> contoh di dalam C: true
-
Uji B saja, format jelek "baru (B (Objek))" -> di luar contoh B: salah, yang satu ini gagal
dari B -> contoh di dalam B: true

Tautan untuk bermain-main


54
2018-03-10 19:03



Implementasi Sergio Carneiro dan Jon mengharuskan Anda untuk menentukan fungsi initializer untuk semua kecuali satu kelas. Berikut ini adalah versi modifikasi dari fungsi agregasi, yang menggunakan parameter default dalam konstruktor sebagai gantinya. Termasuk juga beberapa komentar oleh saya.

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;
}

Berikut ini sedikit demo:

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.

Fungsi agregasi ini akan lebih memilih properti dan metode kelas yang kemudian muncul di daftar kelas.


11
2017-07-26 16:45



Ini tidak mungkin dengan cara kerja warisan prototipikal. Mari kita lihat cara kerja alat peraga yang diwarisi di 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

mari kita lihat apa yang terjadi ketika Anda mengakses properti yang tidak ada:

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

Kamu dapat memakai mixins untuk mendapatkan beberapa fungsi itu tetapi Anda tidak akan terlambat mengikat:

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

8
2018-04-26 15:11



Justin Fagnani menjelaskan cara yang sangat bersih (imho) untuk menyusun beberapa kelas menjadi satu menggunakan fakta bahwa di ES2015, kelas dapat dibuat dengan kelas ekspresi.

Ekspresi vs deklarasi

Pada dasarnya, sama seperti Anda dapat membuat fungsi dengan ekspresi:

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

Anda dapat melakukan hal yang sama dengan kelas:

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

Ekspresi dievaluasi saat runtime, ketika kode dijalankan, sedangkan deklarasi dijalankan sebelumnya.

Menggunakan ekspresi kelas untuk membuat mixin

Anda dapat menggunakan ini untuk membuat fungsi yang secara dinamis membuat kelas hanya ketika fungsi tersebut disebut:

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

Yang menarik adalah Anda dapat menentukan seluruh kelas sebelumnya dan hanya memutuskan kelas mana yang harus diperluas pada saat Anda memanggil fungsi:

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

Jika Anda ingin menggabungkan beberapa kelas bersama, karena kelas ES6 hanya mendukung pewarisan tunggal, Anda perlu membuat rangkaian kelas yang berisi semua kelas yang ingin Anda gabungkan bersama. Jadi katakanlah Anda ingin membuat kelas C yang memperluas A dan B, Anda bisa melakukan ini:

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

Masalahnya adalah ini sangat statis. Jika Anda kemudian memutuskan Anda ingin membuat kelas D yang memperluas B tetapi tidak A, Anda punya masalah.

Tetapi dengan beberapa trik pintar menggunakan fakta bahwa kelas dapat berupa ekspresi, Anda dapat menyelesaikannya dengan membuat A dan B tidak langsung sebagai kelas, tetapi sebagai pabrik kelas (menggunakan fungsi panah untuk keringkasan):

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)

Perhatikan bagaimana kita hanya memutuskan pada saat-saat terakhir kelas mana yang akan dimasukkan dalam hierarki.

Bantu kami membangun masa depan!

Tentu saja jika Anda seperti saya, ini menginspirasi Anda untuk membangun perpustakaan utama untuk multiple inheritance di Javascript. Jika Anda siap, tolong bantu saya melakukan hal itu! Lihat proyek ini dan bantu jika Anda bisa!

mic

mic (ucapkan: mix) adalah pustaka yang membuat banyak warisan dalam Javascript menjadi mudah. Terinspirasi oleh posting blog yang sangat baik "Real" Mixins dengan Kelas Javascript oleh Justin Fagnani, mikrofon mencoba untuk membangun perpustakaan minimal di sekitar konsep menggunakan ekspresi kelas (pabrik) sebagai mixins. mic memperluas konsep yang disajikan dalam posting blog dengan membuat mixins warga kelas satu yang dapat langsung digunakan untuk instantiate objek dan dapat dicampur dengan mixin lain, bukan hanya dengan kelas.


4
2017-10-02 11:31



Saya datang dengan solusi ini:

'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;
}

pemakaian:

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'
            ]
        };
    }
}

Selama Anda membuat trik ini dengan kelas yang ditulis secara khusus Anda dapat dirantai. tetapi kami segera karena Anda ingin memperluas beberapa fungsi / kelas yang ditulis tidak seperti itu - Anda tidak akan memiliki kesempatan untuk melanjutkan pengulangan.

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

bekerja untuk saya di node v5.4.1 dengan bendera --harmony


2
2018-01-19 00:00



Dari halaman es6-features.org/#ClassInheritanceFromExpressions, dimungkinkan untuk menulis fungsi agregasi untuk memungkinkan multiple inheritance:

class Rectangle memperluas agregasi (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
}

Tapi itu sudah disediakan di perpustakaan seperti  pengumpulan.


2
2018-04-01 14:25



gunakan Mixins untuk ES6 multiple Inheritence.

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

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

1
2017-09-29 08:33