ES6-Klasse Mehrfachvererbung

133

Ich habe den größten Teil meiner Recherchen zu BabelJS und MDN (das überhaupt keine Informationen enthält) durchgeführt. Sie können mir jedoch gerne mitteilen, ob ich nicht sorgfältig genug nach weiteren Informationen zur ES6-Spezifikation gesucht habe.

Ich frage mich, ob ES6 die Mehrfachvererbung auf dieselbe Weise unterstützt wie andere Sprachen vom Typ Ente. Kann ich zum Beispiel so etwas tun:

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

mehrere Klassen auf die neue Klasse erweitern? Wenn ja, bevorzugt der Interpreter Methoden / Eigenschaften von ClassTwo gegenüber ClassOne?

BTC
quelle
4
Dies ist nicht wirklich möglich mit der aktuellen Art und Weise, wie die Vererbung in js funktioniert. Das nächste, was Sie tun können, ist ein Mixin
QWertymk
Können Sie eine Referenz angeben, die besagt, dass dies in der neuen Spezifikation nicht möglich ist, und wenn ja, können Sie eine Antwort darauf geben, damit ich sie akzeptieren kann?
BTC
Ich habe gelesen, dass die neuen ES6-Klassen keine neuen Funktionen hinzufügen, sondern nur Syntaxzucker sind.
Oriol
@Oriol, sie sind Syntaxzucker, aber ich hatte mich gefragt, ob dieser Zucker intern etwas mit mehreren Klassen macht.
BTC

Antworten:

70

Ein Objekt kann nur einen Prototyp haben. Das Erben von zwei Klassen kann durch Erstellen eines übergeordneten Objekts als Kombination von zwei übergeordneten Prototypen erfolgen.

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

Spitze
quelle
1
Ich habe mich immer gefragt, ob es eine Möglichkeit gibt, einen Getter auf den __proto__Link zu setzen, um die Requisitensuche an das richtige Objekt weiterzuleiten. Ich habe es versucht, aber noch nie zum
Laufen gebracht
3
@qwertymk denken Sie daran, dass __proto__selbst eine veraltete Funktion ist. Es spiegelt die interne Prototypverbindung wider, aber es ist nicht wirklich die interne Prototypverbindung.
Pointy
Also nie eine Chance, dass so ein Hack jemals funktioniert? Core-JS hat mit der Unterstützung von Schwachkarten mithilfe von Gettern etwas Ähnliches gemacht. Mehrfachvererbung wäre sehr cool
qwertymk
1
@qwertymk Nun, ich kann nicht mit Autorität sagen, ob es definitiv unmöglich ist. Persönlich verwende ich Vererbung in JavaScript sehr, sehr selten. Tatsächlich benutze ich Prototypen eigentlich ziemlich selten.
Pointy
2
Hier ist die Lösung, die ich gefunden habe: esdiscuss.org/topic/symbol-for-modifying-property-lookup . Beispiel : class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. Die Methoden und Eigenschaften des letzten übergebenen Konstruktors new MultiClasshaben höchste Priorität. Sie werden lediglich in den neuen Prototyp eingemischt. Ich denke, es gibt eine noch bessere Lösung, wenn sie mit ES6 Proxies erneut implementiert wird, aber es gibt noch nicht genug native Unterstützung dafür.
Trusktr
87

Überprüfen Sie mein Beispiel unten, superMethode funktioniert wie erwartet. Mit ein paar Tricks instanceoffunktioniert es sogar (meistens):

// 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 -> außerhalb der Instanz von D: true
von A -> innerhalb der Instanz von A: true
von B -> innerhalb der Instanz von B: true
von C -> innerhalb der Instanz von C: true
von D -> innerhalb der Instanz von D: true
- -
Test E: erweitert A, C -> außerhalb der Instanz von E: true
von A -> innerhalb der Instanz von A: true
von C -> innerhalb der Instanz von C: true
von E -> innerhalb der Instanz von E: true
- -
Test F: erweitert B -> außerhalb der Instanz von F: true
von B -> innerhalb der Instanz von B: true
von F -> innerhalb der Instanz von F: true
- -
Test G: Wraper, um C alleine mit "neuem" Dekorator zu verwenden, hübsches Format -> außerhalb der Instanz von G: true
von C -> innerhalb der Instanz von C: true
- -
Test B alleine, hässliches Format "new (B (Object))" -> außerhalb der Instanz von B: false, dieses schlägt fehl
von B -> innerhalb der Instanz von B: true

Link zum Herumspielen

Poelinca Dorin
quelle
1
Sie können dieses "hässliche Format" von B (Objekt) beheben, indem Sie B erweitern (B||Object).
Aaron
@ Aaron Ich bin mir nicht sicher, ob ich dir in diesem Fall folge (oder du folgst mir). Wenn F extends (B||Object)stattdessen F extends B(Object), wird das B-Mixin so erweitert, wie es ist (als Funktion), sodass F nur den Standardfunktionsprototyp erweitert, da B nie ausgeführt wurde. Mit verwenden F extends B(Object)wir tatsächlich die B-Funktion und F erweitert 'was auch immer' B-Funktion zurückgibt. In diesem Fall ist es die B-Klasse, die in der B-Funktion definiert ist ... kleiner Hack, um die Klassennamen richtig zu halten.
Poelinca Dorin
@ Aaron, was wir tun könnten, ist Funktion Standardparameter zu verwenden const B = (B = Object) => class extends B {und dann class F extends B() {für eine schönere Verwendung zu verwenden, aber hässlicher Hack Kappa
Poelinca Dorin
const B = (B) => class extends (B||Object) {würde Sie ersetzen inst5 = new (B(Object)); // instance only B, ugly formatmit inst5 = new (B());, oder vielleicht ich falsch verstehen den Kontext ...
Aaron
@ Aaron ja das würde gut funktionieren bis die console.log('from B -> inside instance of B: ${this instanceof B}');Hexe als scheitert Right-hand side of 'instanceof' is not an object. Die Verwendung const B = (B = Object) => class extends B {wie zuvor erwähnt besteht die Testinstanz und bietet Ihnen auch die inst5 = new (B());Verwendung, wenn Sie dies wünschen.
Poelinca Dorin
23

Für die Implementierung von Sergio Carneiro und Jon müssen Sie eine Initialisierungsfunktion für alle bis auf eine 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 angezeigt werden.

Chong Lip Phang
quelle
3
Wenn ich versuche, dies mit reagieren zu verwenden Component, funktioniert es nicht. Nur zu Ihrer Information an alle anderen, die es zu diesem Zweck gewollt haben könnten.
r3wt
Das überschreibt gleichnamige Variablen und Funktionen.
Vincent Hoch-Drei
17

Justin Fagnani beschreibt einen sehr sauberen (imho) Weg , um mehrere Klassen in einem mit der Tatsache zu komponieren , dass in ES2015 können Klassen mit Klasse erstellt werden Ausdrücke .

Ausdrücke gegen Erklärungen

Grundsätzlich können Sie eine Funktion mit einem Ausdruck erstellen:

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

Sie können dasselbe 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 zuvor eine Deklaration ausgeführt wird.

Verwenden von Klassenausdrücken zum Erstellen von Mixins

Mit dieser Option können Sie eine Funktion erstellen, die nur dann dynamisch eine Klasse erstellt, wenn die Funktion aufgerufen wird:

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

Das Coole daran ist, dass Sie die gesamte Klasse im Voraus definieren und nur entscheiden können, welche Klasse bis zum Aufruf der Funktion erweitert werden soll:

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

Wenn Sie mehrere Klassen miteinander mischen möchten, da ES6-Klassen nur eine einzige Vererbung unterstützen, müssen Sie eine Klassenkette erstellen, die alle Klassen enthält, die Sie zusammen mischen möchten. Angenommen, Sie möchten eine Klasse C erstellen, die sowohl A als auch B erweitert. Sie können 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.

Mit einigen cleveren Tricks, bei denen Klassen Ausdrücke sein können, können Sie dies lösen, indem Sie A und B nicht direkt als Klassen, sondern als Klassenfabriken erstellen (der Kürze halber 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.

Stijn de Witt
quelle
8

Dies ist mit der Funktionsweise der prototypischen Vererbung nicht wirklich möglich. Schauen wir uns an, 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

Mal sehen, was passiert, wenn Sie auf eine Requisite zugreifen, die es nicht gibt:

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 Mixins verwenden , um einige dieser Funktionen zu erhalten, aber Sie werden keine späte Bindung erhalten:

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
qwertymk
quelle
Akzeptiere die Antwort von @ Pointy, weil er über das erweiterte Schlüsselwort gesprochen hat, um das es in der eigentlichen Frage ging, und nicht um Vererbungsmuster, aber danke, dass du dich dafür interessiert hast!
BTC
2

Ich habe mir diese Lösung ausgedacht:

'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 verkettet werden. Aber wenn Sie eine nicht so geschriebene Funktion / Klasse erweitern möchten, haben Sie keine Chance, die 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 dem Flag --harmony

Maikal
quelle
Ich glaube nicht, dass Sie ein Harmonie-Flag für Knoten 4x und höher benötigen.
Umayr
2

Auf der Seite es6-features.org/#ClassInheritanceFromExpressions kann eine Aggregationsfunktion geschrieben werden, um Mehrfachvererbung zu ermöglichen:

Klasse Rectangle erweitert die Aggregation (Shape, Coloured, 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
}

Dies ist jedoch bereits in Bibliotheken wie der Aggregation vorgesehen .

Sergio Carneiro
quelle
1

Verwenden Sie Mixins für ES6 Multiple Inheritence.

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

class Example extends classTwo(ClassOne) {
    constructor() {
    }
}
Nr8
quelle
3
soll Mehrfachvererbung nicht bedeuten one class inherits from 2 or more unrelated classes? Was Ihr Beispiel zeigt, ist eine Klasse, die von 2 erbt, aber verwandte Klassen. Dies ist eine Einzelvererbung, keine Mehrfachvererbung.
Vlad-Ardelean
@ vlad-ardelean Eigentlich ist die Beziehung künstlich, dh. dynamisch durch Aufruf eingerichtet classTwo. Ohne ein echtes Klassenkonzept hat JS sowieso keine strukturelle Vererbung. Auf Anhieb kann ich mir kein JS-Szenario vorstellen, in dem sich Mixins anders verhalten als erwartet, wenn man sie als MI aus der wahren OO-Welt betrachtet (abgesehen von der definierten 'Super'-Kette). Vielleicht kann eine Person, die besser informiert ist als ich, eine liefern.
Kollapsar
@collapsar Ich denke du hast absolut recht. JS hat eine prototypische Vererbung, was bedeutet, dass es eine Prototypkette gibt, in der jeder Prototyp in der Kette einen einzigen Elternteil hat. Wenn eine ganze Reihe von Klassen in einer definierten Reihenfolge in die Prototypenkette eingemischt wird, entspricht dies praktisch dem MI in der OO-Welt.
Stijn de Witt
1

Nun, Object.assign bietet Ihnen die Möglichkeit, etwas Nahes zu tun, wenn auch ein bisschen 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 nicht gesehen, dass dies irgendwo verwendet wird, aber es ist tatsächlich ziemlich nützlich. Sie können function shark(){}anstelle von Klasse verwenden, aber es gibt Vorteile, stattdessen Klasse zu verwenden.

Ich glaube, das einzige, was sich bei der Vererbung mit extendSchlüsselwörtern unterscheidet, ist, dass die Funktion nicht nur auf prototypedem Objekt, sondern auch auf dem Objekt selbst lebt .

Wenn Sie dies tun, verfügt new Shark()das sharkErstellte über eine biteMethode, während nur der Prototyp über eine eatMethode verfügt

Ced
quelle
Das wird nicht funktionieren. Prototypmethoden werden nicht eingemischt und die Bindung ist falsch.
Jonschlinkert
1

Es gibt keine einfache Möglichkeit, eine Vererbung mehrerer Klassen durchzuführen. 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());

Hoffe das ist hilfreich.

AnandShanbhag
quelle
1

Diese ES6-Lösung hat bei mir funktioniert:

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

Ausbeuten auf der Browserkonsole:

ab.sayA() = ab, ab.sayB() = ab, ab.sayAB() = ab
user2006754
quelle
ES6 ist JavaScript!
Bergi
1

Ich habe eine halbe Woche damit verbracht, dies selbst herauszufinden, und einen ganzen Artikel darüber geschrieben, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS , und hoffe, dass es einigen von Ihnen hilft.

Kurz gesagt, wie MI in JavaScript implementiert werden kann:

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

Und hier ist specialize_with () einzeilig:

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

Weitere Informationen finden Sie unter https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS .

Leonid Titov
quelle
1

In Javascript können Sie einer Klasse (Konstruktorfunktion) nicht 2 verschiedene Prototypobjekte geben. Da die Vererbung in Javascript mit dem Prototyp zusammenarbeitet, können Sie nicht mehr als 1 Vererbung für eine Klasse verwenden, aber Sie können die Eigenschaft des Prototype-Objekts und dieser Haupteigenschaft aggregieren und verbinden Innerhalb einer Klasse manuell mit Refactoring dieser übergeordneten Klassen und als nächstes erweitert diese neue Version und verbundene Klasse zu Ihrer Zielklasse haben Code für Ihre Frage:

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

};
Saman
quelle
1

Meine Antwort scheint weniger Code zu sein und funktioniert bei mir:

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!
}
toddmo
quelle
0

Verwenden Sie Extent mit benutzerdefinierter Funktion, um die Mehrfachvererbung mit es6 zu verarbeiten

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)

Jon
quelle
0

Ich werde auch meine Lösung hinzufügen - ich fand sie für mich selbst am freundlichsten, was ich in diesem Thread gelesen habe.

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

Sie können es dann so verwenden:

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

Als Proof of Concept habe ich folgende Funktion ausgeführt. Es nimmt eine Liste von Klassen und setzt sie zu einer neuen Klasse zusammen (der letzte Prototyp gewinnt, damit es keine Konflikte gibt). Beim Erstellen einer zusammengesetzten Funktion kann der Benutzer alle Originalkonstruktoren verwenden [ sic! ] oder ihre eigenen übergeben. Dies war die größte Herausforderung dieses Experiments: eine Beschreibung zu erstellen, was der Konstruktor tun sollte. Das Kopieren von Methoden in einen Prototyp ist kein Problem, sondern die beabsichtigte Logik eines neu zusammengesetzten Objekts. Oder sollte es vielleicht konstruktorlos sein? Soweit ich weiß, findet es in Python den passenden Konstruktor, aber Funktionen in JS sind akzeptabler, daher kann man fast alles an eine Funktion übergeben, und von der Signatur ist es nicht klar.

Ich denke nicht, dass es optimiert ist, aber der Zweck war es, Möglichkeiten zu erkunden. instanceofwird sich nicht wie erwartet verhalten, was, denke ich, ein Mist ist, da klassenorientierte Entwickler dies gerne als Werkzeug verwenden.

Vielleicht hat JavaScript es einfach nicht.

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

Ursprünglich hier gepostet (gist.github.com).

Der Zeuge
quelle
-3

Hier ist eine großartige / wirklich beschissene Möglichkeit, mehrere Klassen zu erweitern. Ich benutze ein paar Funktionen, die Babel in meinen transpilierten Code eingefügt hat. Die Funktion erstellt eine neue Klasse, die Klasse1 erbt, und Klasse1 erbt Klasse2 usw. Es hat seine Probleme, aber eine lustige Idee.

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) {}
Casey
quelle