Pregunta ES6 Class Multiple inheritance


He hecho la mayor parte de mi investigación sobre esto en BabelJS y en MDN (que no tiene información), pero no dude en decirme si no he tenido la precaución de buscar más información sobre ES6 Spec.

Me pregunto si ES6 admite herencia múltiple de la misma manera que lo hacen otros lenguajes de pato. Por ejemplo, ¿puedo hacer algo como esto?

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

para extender múltiples clases a la nueva clase? Si es así, ¿el intérprete preferirá los métodos / propiedades de ClassTwo sobre ClassOne?


76
2018-04-26 15:03


origen


Respuestas:


Un objeto solo puede tener un prototipo. La herencia de dos clases se puede hacer creando un objeto principal como una combinación de dos prototipos principales.

La sintaxis para la creación de subclases hace posible hacer eso en la declaración, ya que el lado derecho de la extends cláusula puede ser cualquier expresión. Por lo tanto, puede escribir una función que combine prototipos de acuerdo con los criterios que desee y llame a esa función en la declaración de clase.


38
2018-04-26 15:11



Mira mi ejemplo a continuación, super método funcionando como se esperaba Usando algunos trucos, incluso instanceof Trabaja la mayor parte del tiempo):

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

Se imprimirá

Prueba D: extiende A, B, C -> instancia externa de D: verdadero
de A -> instancia interna de A: verdadero
de B -> instancia interna de B: verdadero
de C -> instancia interna de C: verdadero
de D -> instancia interna de D: verdadero
-
Prueba E: extiende A, C -> instancia externa de E: verdadero
de A -> instancia interna de A: verdadero
de C -> instancia interna de C: verdadero
de E -> instancia interna de E: verdadero
-
Prueba F: extiende B -> instancia externa de F: verdadero
de B -> instancia interna de B: verdadero
de F -> instancia interna de F: verdadero
-
Prueba G: wraper para usar solo C con decorador "nuevo", formato bonito -> instancia externa de G: verdadero
de C -> instancia interna de C: verdadero
-
Prueba B solo, formato feo "nuevo (B (Objeto))" -> instancia externa de B: falso, este falla
de B -> instancia interna de B: verdadero

Enlace para violín


54
2018-03-10 19:03



Implementación de Sergio Carneiro y Jon requiere que defina una función de inicialización para todas las clases excepto una. Aquí hay una versión modificada de la función de agregación, que utiliza en su lugar parámetros predeterminados en los constructores. Incluido hay también algunos comentarios míos.

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

Aquí hay una pequeña demostración:

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.

Esta función de agregación preferirá las propiedades y métodos de una clase que aparece más adelante en la lista de clases.


11
2017-07-26 16:45



Esto no es realmente posible con la forma en que funciona la herencia prototípica. Veamos cómo funcionan los accesorios heredados en 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

veamos qué ocurre cuando accedes a un accesorio que no existe:

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

Puedes usar Mixins para obtener parte de esa funcionalidad, pero no obtendrás vinculaciones tardías:

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 describe una forma muy limpia (imho) de componer múltiples clases en una usando el hecho de que en ES2015, las clases se pueden crear con clase expresiones.

Expresiones vs declaraciones

Básicamente, al igual que puedes crear una función con una expresión:

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

puedes hacer lo mismo con las clases:

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

La expresión se evalúa en tiempo de ejecución, cuando se ejecuta el código, mientras que una declaración se ejecuta de antemano.

Usar expresiones de clase para crear mixins

Puede usar esto para crear una función que crea dinámicamente una clase solo cuando se llama a la función:

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

Lo bueno de esto es que puedes definir toda la clase de antemano y solo decidir en qué clase debe extenderse cuando llames a la función:

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

Si desea mezclar varias clases juntas, dado que las clases ES6 solo son compatibles con la herencia individual, debe crear una cadena de clases que contenga todas las clases que desee combinar. Entonces digamos que quieres crear una clase C que amplíe tanto A como B, podrías hacer esto:

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

El problema con esto es que es muy estático. Si luego decides que quieres hacer una clase D que se extiende B pero no A, tienes un problema.

Pero con algunos trucos inteligentes usando el hecho de que las clases pueden ser expresiones, puedes resolver esto creando A y B no directamente como clases, sino como fábricas de clase (usando las funciones de flecha para abreviar):

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)

Observe cómo solo decidimos en el último momento qué clases incluir en la jerarquía.

¡Ayúdanos a construir el futuro!

Por supuesto, si eres como yo, esto te inspira a construir la biblioteca definitiva para la herencia múltiple en Javascript. Si estás preparado, ¡ayúdame a hacer exactamente eso! ¡Mira este proyecto y ayuda si puedes!

micrófonos

micrófonos (pronuncie: mix) es una biblioteca que hace que la herencia múltiple en Javascript sea muy fácil. Inspirado en la excelente publicación de blog Mixins "reales" con clases de Javascript de Justin Fagnani, los micrófonos intentan crear una biblioteca mínima sobre el concepto de usar expresiones de clases (fábricas) como mixins. los micrófonos amplían los conceptos presentados en la publicación de blog al hacer que los mixins sean ciudadanos de primera clase que se pueden usar directamente para crear instancias de objetos y se pueden mezclar con otros mixins en lugar de simplemente con clases.


4
2017-10-02 11:31



Yo propongo esta solución:

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

uso:

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

Siempre que haga este truco con sus clases personalizadas, puede encadenarse. pero nosotros tan pronto como desee extender alguna función / clase escrita no así, no tendrá la oportunidad de continuar con el ciclo.

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

funciona para mí en el nodo v5.4.1 con --harmony flag


2
2018-01-19 00:00



De la página es6-features.org/#ClassInheritanceFromExpressions, es posible escribir una función de agregación para permitir herencia múltiple:

class Rectangle extends 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
}

Pero eso ya se proporciona en bibliotecas como  agregación.


2
2018-04-01 14:25



use Mixins para ES6 multiple Herencia.

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

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

1
2017-09-29 08:33