Frage ES6-Klasse Mehrfachvererbung


Ich habe die meiste Zeit darüber geforscht BabelJS und weiter MDN (die keine Informationen enthält), aber bitte zögern Sie nicht, mir zu sagen, wenn ich nicht vorsichtig genug gewesen bin, um nach weiteren Informationen über die ES6 Spec zu suchen.

Ich frage mich, ob ES6 die Mehrfachvererbung auf die gleiche Weise unterstützt, wie dies bei anderen duck-typed Sprachen der Fall ist. Zum Beispiel, kann ich etwas tun wie:

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

um mehrere Klassen auf die neue Klasse zu erweitern? Wenn ja, wird der Dolmetscher Methoden / Eigenschaften von ClassTwo gegenüber ClassOne bevorzugen?


76
2018-04-26 15:03


Ursprung


Antworten:


Ein Objekt kann nur einen Prototyp haben. Das Vererben von zwei Klassen kann durch Erstellen eines Elternobjekts als eine Kombination von zwei Elternprototypen erfolgen.

Die Syntax für Unterklassen ermöglicht dies in der Deklaration, da die rechte Seite der extends Klausel kann ein beliebiger Ausdruck sein. Sie können also eine Funktion schreiben, die Prototypen nach beliebigen Kriterien kombiniert, und diese Funktion in der Klassendeklaration aufrufen.


38
2018-04-26 15:11



Überprüfen Sie mein Beispiel unten, super Methode funktioniert wie erwartet. Mit ein paar Tricks sogar instanceof funktioniert (die meiste Zeit):

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

Wird ausgedruckt

Test D: erweitert A, B, C -> äußere Instanz von D: wahr
von A -> Inneninstanz von A: wahr
von B -> innerer Fall von B: wahr
von C -> Innere Instanz von C: wahr
von D -> inneres Beispiel von D: wahr
-
Test E: erweitert A, C -> äußere Instanz von E: wahr
von A -> Inneninstanz von A: wahr
von C -> Innere Instanz von C: wahr
von E -> inneres Beispiel von E: wahr
-
Test F: erweitert B -> äußere Instanz von F: wahr
von B -> innerer Fall von B: wahr
von F -> inneres Beispiel von F: wahr
-
Test G: Wrapper um C alleine mit "new" Decorator zu verwenden, hübsches Format -> äußere Instanz von G: true
von C -> Innere Instanz von C: wahr
-
Test B allein, hässliches Format "neu (B (Objekt))" -> äußere Instanz von B: falsch, dieses fehlschlägt
von B -> innerer Fall von B: wahr

Link zum herumspielen


54
2018-03-10 19:03



Sergio Carneiros und Jons Umsetzung erfordert, dass Sie eine Initialisierungsfunktion für alle außer einer Klasse definieren. Hier ist eine modifizierte Version der Aggregationsfunktion, die stattdessen Standardparameter in den Konstruktoren verwendet. Enthalten sind auch einige Kommentare von mir.

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

Hier ist eine kleine 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.

Diese Aggregationsfunktion bevorzugt Eigenschaften und Methoden einer Klasse, die später in der Klassenliste erscheint.


11
2017-07-26 16:45



Dies ist mit der Art der prototypischen Vererbung nicht wirklich möglich. Werfen wir einen Blick darauf, wie geerbte Requisiten in js funktionieren

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

Lass uns sehen, was passiert, wenn du auf eine Requisite zugreifst, die nicht existiert:

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

Sie können verwenden Mixins um etwas von dieser Funktionalität zu bekommen, aber Sie werden keine späte Bindung bekommen:

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 beschreibt eine sehr saubere (imho) Möglichkeit, mehrere Klassen zu einem zusammenzufassen, indem die Tatsache verwendet wird, dass in ES2015 Klassen mit Klasse erstellt werden können Ausdrücke.

Ausdrücke gegen Erklärungen

Genau wie Sie eine Funktion mit einem Ausdruck erstellen können:

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

Sie können das gleiche mit Klassen tun:

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

Der Ausdruck wird zur Laufzeit ausgewertet, wenn der Code ausgeführt wird, während vorher eine Deklaration ausgeführt wird.

Verwenden von Klassenausdrücken zum Erstellen von Mixins

Sie können dies verwenden, um eine Funktion zu erstellen, die eine Klasse nur dann dynamisch erstellt, wenn die Funktion aufgerufen wird:

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

Das Tolle daran ist, dass Sie die gesamte Klasse vorher definieren können und erst beim Aufruf der Funktion entscheiden, welche Klasse sie erweitern soll:

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

Wenn Sie mehrere Klassen zusammen mischen möchten, weil ES6-Klassen nur die einfache Vererbung unterstützen, müssen Sie eine Kette von Klassen erstellen, die alle Klassen enthält, die Sie zusammen mischen möchten. Nehmen wir an, Sie möchten eine Klasse C erstellen, die sowohl A als auch B erweitert. Sie könnten dies tun:

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

Das Problem dabei ist, dass es sehr statisch ist. Wenn Sie später entscheiden, dass Sie eine Klasse D erstellen möchten, die B aber nicht A erweitert, haben Sie ein Problem.

Aber mit etwas schlauen Tricksereien, die die Tatsache verwenden, dass Klassen Ausdrücke sein können, können Sie dies lösen, indem Sie A und B nicht direkt als Klassen, sondern als Klassenfactories erstellen (aus Gründen der Kürze mit Pfeilfunktionen):

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)

Beachten Sie, dass wir erst im letzten Moment entscheiden, welche Klassen in die Hierarchie aufgenommen werden sollen.

Helfen Sie uns, die Zukunft zu bauen!

Natürlich inspiriert Sie, wenn Sie wie ich sind, die ultimative Bibliothek für die Mehrfachvererbung in Javascript zu bauen. Wenn Sie dem gewachsen sind, helfen Sie mir bitte dabei! Sieh dir dieses Projekt an und hilf ihm, wenn du kannst!

Mikrofone

Mikrofone (aussprechen: mischen) ist eine Bibliothek, die mehrfache Vererbung in Javascript ein Kinderspiel macht. Inspiriert durch den exzellenten Blog-Beitrag "Real" Mixins mit Javascript-Klassen von Justin Fagnani, versucht mic, eine minimale Bibliothek um das Konzept der Verwendung von Klassenausdrücken (Fabriken) als Mixins aufzubauen. mics erweitert die im Blog-Post vorgestellten Konzepte, indem es die Mixins zu erstklassigen Bürgern macht, die direkt zur Instanziierung von Objekten verwendet werden können und mit anderen Mixins anstatt nur mit Klassen gemischt werden können.


4
2017-10-02 11:31



Ich habe folgende Lösung:

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

Verwendung:

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

Solange Sie diesen Trick mit Ihren speziell geschriebenen Klassen machen, kann er angekettet werden. aber uns bald, wie Sie eine Funktion / Klasse nicht so erweitern möchten - Sie haben keine Chance, Schleife fortzusetzen.

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

funktioniert für mich in Knoten v5.4.1 mit --harmony flag


2
2018-01-19 00:00



Von der Seite es6-features.org/#ClassInheritanceFromExpressionsEs ist möglich, eine Aggregationsfunktion zu schreiben, um Mehrfachvererbung zu ermöglichen:

class Rectangle erweitert die Aggregation (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
}

Aber das ist schon in Bibliotheken wie  Anhäufung.


2
2018-04-01 14:25



Verwenden Sie Mixins für ES6 multiple Inheritenz.

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

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

1
2017-09-29 08:33



Gut Objekt.zuweisen gibt Ihnen die Möglichkeit, etwas ähnliches zu tun, wenn auch etwas mehr wie Komposition mit ES6-Klassen.

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

Ich habe das nirgendwo benutzt gesehen, aber es ist wirklich sehr nützlich. Sie können verwenden function shark(){}anstelle der Klasse, aber es gibt Vorteile der Verwendung der Klasse.

Ich glaube das einzige, was mit Vererbung anders ist extend Stichwort ist, dass die Funktion nicht nur auf der prototype aber auch das Objekt selbst.

Also jetzt, wenn du es tust new Shark() das shark erstellt hat a bite Methode, während nur sein Prototyp hat eine eat Methode


1
2017-07-11 03:17



Es gibt keine einfache Möglichkeit, mehrere Klassen zu vererben. Ich folge der Kombination von Assoziation und Vererbung, um diese Art von Verhalten zu erreichen.

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

Ich hoffe, das ist hilfreich.


1
2017-09-29 11:50



Diese ES6-Lösung hat für mich funktioniert:

Mehrfachvererbung.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())

Erträge in der Browser-Konsole:

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

1
2018-01-10 10:38