Classe ES6 Eredità multipla


134

Ho fatto la maggior parte delle mie ricerche su questo su BabelJS e su MDN (che non ha alcuna informazione), ma non esitate a dirmi se non sono stato abbastanza attento a cercare in giro ulteriori informazioni sulla specifica ES6.

Mi chiedo se ES6 supporti o meno l'ereditarietà allo stesso modo di altri linguaggi tipizzati da anatra. Ad esempio, posso fare qualcosa del tipo:

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

estendere più classi alla nuova classe? In tal caso, l'interprete preferirà metodi / proprietà da ClassTwo a ClassOne?


4
Questo non è davvero possibile con l'attuale modo in cui l'ereditarietà funziona in js, il più vicino che puoi fare è un mixin
qwertymk,

Puoi fornire qualche tipo di riferimento che afferma che ciò non è possibile nelle nuove specifiche e, in tal caso, puoi renderlo una risposta in modo che io possa accettarlo?
BTC,

Ho letto che le nuove classi ES6 non aggiungono alcuna nuova funzionalità, sono solo zucchero di sintassi.
Oriol,


@Oriol, sono zucchero di sintassi, ma mi ero chiesto se quello zucchero stesse facendo qualcosa con più classi internamente.
BTC,

Risposte:


70

Un oggetto può avere solo un prototipo. L'ereditarietà da due classi può essere effettuata creando un oggetto padre come una combinazione di due prototipi padre.

La sintassi per la sottoclasse rende possibile farlo nella dichiarazione, poiché il lato destro della extendsclausola può essere qualsiasi espressione. Pertanto, è possibile scrivere una funzione che combina i prototipi in base ai criteri desiderati e chiamare tale funzione nella dichiarazione di classe.


1
Mi sono sempre chiesto, c'è un modo per impostare un getter sul __proto__collegamento per inoltrare la ricerca prop all'oggetto corretto? Ci ho provato ma non l'ho mai fatto funzionare
qwertymk,

3
@qwertymk tieni bene a mente che __proto__è di per sé una caratteristica obsoleta. Riflette il collegamento prototipo interno, ma non è in realtà il collegamento prototipo interno.
Punta appuntita

quindi mai nessuna possibilità che un hack del genere funzioni mai? core-js ha fatto qualcosa di simile con il supporto di mappa debole usando getter. L'eredità multipla sarebbe molto interessante
qwertymk,

1
@qwertymk bene non posso dire con autorità se è assolutamente impossibile. Personalmente utilizzo l'ereditarietà in JavaScript molto, molto raramente. In effetti, uso i prototipi piuttosto raramente, del resto.
Punta appuntita

2
Ecco la soluzione che mi è venuta in mente: esdiscuss.org/topic/symbol-for-modifying-property-lookup . Campione: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. I metodi e le proprietà dell'ultimo costruttore sono passati per new MultiClassavere la massima precedenza, sono appena miscelati nel nuovo prototipo. Penso che esista una soluzione ancora migliore se reimplementata usando ES6 Proxies, ma non c'è ancora abbastanza supporto nativo per esso.
trusktr,

89

Controlla il mio esempio di seguito, supermetodo che funziona come previsto. L'uso di alcuni trucchi instanceoffunziona anche (la maggior parte delle volte):

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

Stamperà

Test D: estende A, B, C -> istanza esterna di D: true
da A -> istanza interna di A: true
da B -> istanza interna di B: vero
da C -> istanza interna di C: true
da D -> istanza interna di D: true
-
Test E: estende A, C -> istanza esterna di E: true
da A -> istanza interna di A: true
da C -> istanza interna di C: true
da E -> istanza interna di E: vero
-
Test F: estende B -> istanza esterna di F: true
da B -> istanza interna di B: vero
da F -> istanza interna di F: vero
-
Test G: wrapper per usare C da solo con il "nuovo" decoratore, formato grazioso -> istanza esterna di G: true
da C -> istanza interna di C: true
-
Prova solo B, formato brutto "new (B (Object))" -> istanza esterna di B: false, questa fallisce
da B -> istanza interna di B: vero

Link per giocherellare


1
È possibile risolvere questo "brutto formato" di B (Object), rendendo B si estendono (B||Object).
Aaron,

@Aaron non sono proprio sicuro di seguirti su questo (o mi stai seguendo). Se F extends (B||Object)invece F extends B(Object), estenderà il mixin B così com'è (come una funzione), quindi F estenderà il prototipo di funzione predefinito poiché B non è mai stato eseguito. Usando F extends B(Object)stiamo effettivamente eseguendo la funzione B e F estenderà 'qualunque' funzione B restituita, in questo caso è la classe B definita all'interno della funzione B ... piccolo hack per mantenere il nome della classe giusto.
Poelinca Dorin,

@Aaron quello che potremmo fare è usare i parametri di default della funzione const B = (B = Object) => class extends B {e poi usarli class F extends B() {per un uso più
carino

const B = (B) => class extends (B||Object) {avrebbe lasciato che si sostituisce inst5 = new (B(Object)); // instance only B, ugly formatcon inst5 = new (B());, o forse mi fraintendono il contesto ...
Aaron

@Aaron sì che funzionerebbe bene fino a quando la console.log('from B -> inside instance of B: ${this instanceof B}');strega fallirà Right-hand side of 'instanceof' is not an object. L'utilizzo const B = (B = Object) => class extends B {come precedentemente indicato supererà l'istanza di test e ti fornirà anche l' inst5 = new (B());uso se lo desideri.
Poelinca Dorin,

23

L'implementazione di Sergio Carneiro e Jon richiede di definire una funzione di inizializzazione per tutte le classi tranne una. Ecco una versione modificata della funzione di aggregazione, che utilizza invece i parametri predefiniti nei costruttori. Sono inclusi anche alcuni commenti da parte mia.

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

Ecco una piccola 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.

Questa funzione di aggregazione preferirà le proprietà e i metodi di una classe che appariranno più avanti nell'elenco delle classi.


3
quando provo ad usare questo con reagire Component, non funziona. Cordiali saluti a chiunque altro avrebbe potuto desiderarlo per questo scopo.
r3wt

Ciò sovrascrive variabili e funzioni che hanno lo stesso nome.
Vincent Hoch-Drei l'

17

Justin Fagnani descrive un modo molto pulito (imho) di comporre più classi in una usando il fatto che in ES2015, le classi possono essere create con espressioni di classe .

Espressioni vs dichiarazioni

Fondamentalmente, proprio come puoi creare una funzione con un'espressione:

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

puoi fare lo stesso con le classi:

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

L'espressione viene valutata in fase di esecuzione, quando viene eseguito il codice, mentre una dichiarazione viene eseguita in anticipo.

Utilizzo delle espressioni di classe per creare mixin

Puoi usarlo per creare una funzione che crea dinamicamente una classe solo quando la funzione è chiamata:

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

La cosa interessante è che puoi definire in anticipo l'intera classe e decidere solo su quale classe estendere al momento in cui chiami la funzione:

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

Se si desidera mescolare più classi insieme, poiché le classi ES6 supportano solo la singola eredità, è necessario creare una catena di classi che contenga tutte le classi che si desidera combinare. Quindi supponiamo che tu voglia creare una classe C che estende sia A che B, potresti farlo:

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

Il problema è che è molto statico. Se in seguito decidi di voler creare una classe D che estende B ma non A, hai un problema.

Ma con qualche astuzia che utilizza il fatto che le classi possono essere espressioni, puoi risolverlo creando A e B non direttamente come classi, ma come fabbriche di classe (usando le funzioni freccia per brevità):

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)

Nota come decidiamo solo all'ultimo momento quali classi includere nella gerarchia.


8

Questo non è realmente possibile con il modo in cui funziona l'eredità prototipica. Diamo un'occhiata a come funzionano gli oggetti di scena ereditati in 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

vediamo cosa succede quando accedi a un oggetto che non esiste:

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

Puoi utilizzare i mixin per ottenere alcune di quelle funzionalità, ma non otterrai l'associazione tardiva:

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

Accettando la risposta di @ Pointy perché ha parlato della parola chiave extends, che è la vera domanda incorniciata e non i modelli di ereditarietà, ma grazie per l'interesse!
BTC,

2

Ho trovato queste soluzioni:

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

utilizzo:

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

Fintanto che stai facendo questi trucchi con le tue classi scritte in modo personalizzato, può essere incatenato. ma non appena vorrai estendere alcune funzioni / classi scritte in questo modo, non avrai alcuna possibilità di continuare il ciclo.

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

funziona per me nel nodo v5.4.1 con --harmony flag


Non penso che tu abbia bisogno della bandiera dell'armonia per il nodo 4x e superiore.
Umayr,

2

Dalla pagina es6-features.org/#ClassInheritanceFromExpressions , è possibile scrivere una funzione di aggregazione per consentire l'ereditarietà multipla:

class Rectangle estende l'aggregazione (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
}

Ma questo è già fornito in biblioteche come l' aggregazione .


1

utilizzare Mixin per l'ereditarietà multipla ES6.

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

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

3
l'eredità multipla non dovrebbe significare one class inherits from 2 or more unrelated classes? Ciò che mostra il tuo esempio è una classe che eredita da 2, ma classi correlate. Questa è eredità singola, non eredità multipla.
vlad-ardelean,

@ vlad-ardelean In realtà la relazione è artificiale, cioè. stabilito dinamicamente chiamando classTwo. Mancando di un vero concetto di classe, JS non ha comunque eredità strutturale . A prima vista non riesco a concepire uno scenario JS in cui i mixin si comportano in modo diverso da quello che ti aspetteresti concettualizzandoli come MI dal vero mondo OO (a parte la catena super definita); forse qualcuno più esperto di me può fornirne uno.
collapsar,

@collapsar Penso che tu abbia assolutamente ragione. JS ha un'eredità prototipica, il che significa che esiste una catena di prototipi in cui ogni prototipo della catena ha un unico genitore. Quando si mescolano un intero gruppo di classi nella catena di prototipi in un ordine definito, è effettivamente lo stesso di MI nel mondo OO.
Stijn de Witt,

1

Bene Object.assign ti dà la possibilità di fare qualcosa di simile anche se un po 'più simile alla composizione con le classi 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();

Non l'ho visto usato da nessuna parte ma in realtà è abbastanza utile. Puoi usarefunction shark(){} posto della classe ma ci sono vantaggi nell'usare invece la classe.

Credo che l'unica cosa diversa con l'ereditarietà con la extendparola chiave sia che la funzione non vive solo suprototype sull'oggetto ma anche sull'oggetto stesso.

Così ora quando lo fai new Shark()il sharkcreato ha un bitemetodo, mentre solo il suo prototipo ha un eatmetodo


Questo non funzionerà. I metodi prototipo non verranno mescolati e l'associazione sarà errata.
jonschlinkert,

1

Non esiste un modo semplice per ereditare più classi. Seguo la combinazione di associazione ed eredità per raggiungere questo tipo di comportamento.

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

Spero sia utile.


1

Questa soluzione ES6 ha funzionato per me:

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

Rendimento su console browser:

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

ES6 è JavaScript!
Bergi,

1

Ho trascorso mezza settimana cercando di capirlo da solo, e ho scritto un intero articolo su di esso, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS , e spero che possa aiutare qualcuno di voi.

In breve, ecco come l'IM può essere implementato in 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();

Ed ecco specialize_with () one-liner:

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

Ancora una volta, guarda https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS .


1

in javascript non puoi dare a una classe (funzione di costruzione) 2 diversi oggetti prototipo e poiché l'ereditarietà in javascript funziona con il prototipo, quindi non puoi usare più di 1 eredità per una classe ma puoi aggregare e unire la proprietà dell'oggetto Prototype e quella proprietà principale all'interno di una classe manualmente con il refactoring delle classi padre e successiva estende quella nuova versione e la classe unita alla classe target hanno il codice per la tua domanda:

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

La mia risposta sembra meno codice e funziona per me:

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

usa extension con funzione personalizzata per gestire l'ereditarietà multipla con 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

Aggiungerò anche la mia soluzione - l'ho trovata la più amichevole per me stessa da quello che ho letto in questa discussione.

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

Puoi usarlo quindi in questo modo:

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

0

Come prova del concetto, ho svolto la seguente funzione. Prende un elenco di classi e le compone in una nuova classe (l'ultimo prototipo vince quindi non ci sono conflitti). Quando si crea una funzione composta, l'utente può scegliere di utilizzare tutti i costruttori originali [ sic! ] o passare il proprio. Questa è stata la più grande sfida di questo esperimento: trovare una descrizione di cosa dovrebbe fare il costruttore. La copia dei metodi in un prototipo non è un problema ma qual è la logica prevista per l'oggetto appena composto. O forse dovrebbe essere senza costruttore? In Python, da quello che so, trova la corrispondenza costruttore ma le funzioni in JS sono più accettabili, quindi si può passare a una funzione praticamente tutto e dalla firma non sarà chiaro.

Non credo sia ottimizzato, ma lo scopo era esplorare le possibilità. instanceofnon si comporterà come previsto, il che, immagino, è un peccato, dal momento che agli sviluppatori orientati alla classe piace usare questo come strumento.

Forse JavaScript non ce l'ha proprio.

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

Originariamente pubblicato qui (gist.github.com).



-3

Ecco un modo fantastico / davvero scadente di estendere più classi. Sto usando un paio di funzioni che Babel ha inserito nel mio codice traspilato. La funzione crea una nuova classe che eredita class1 e class1 eredita class2 e così via. Ha i suoi problemi, ma un'idea divertente.

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) {}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.