ES6 Class Multiple inheritance

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

Me pregunto si ES6 admite inheritance 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 classs a la nueva class? Si es así, ¿el intérprete preferirá los methods / properties de ClassTwo sobre ClassOne?

Un object solo puede tener un prototipo. La inheritance de dos classs se puede hacer creando un object principal como una combinación de dos prototypes principales.

La syntax para la creación de subclasss hace posible hacer eso en la statement, ya que el lado derecho de la cláusula extends puede ser cualquier expresión. Por lo tanto, puede escribir una function que combine prototypes de acuerdo con los criterios que desee y llame a esa function en la statement de class.

Verifique mi ejemplo a continuación, el super funciona como se esperaba. Usando algunos trucos, incluso una instanceof trabajos (la mayoría del time):

 // 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", formatting bonito -> instancia externa de G: verdadero
 de C -> instancia interna de C: verdadero
 -
 Prueba B solo, formatting feo "nuevo (B (Objeto))" -> instancia externa de B: falso, este falla
 de B -> instancia interna de B: verdadero

Enlace para violín

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

Puede usar mixins para get parte de esa funcionalidad, pero no obtendrá un enlace tardío:

 var a = {x: '1'}; var b = {y: '2'}; var c = createWithMixin([a, b]); cx; // 1 cy; // 2 bz = 3; cz; // undefined 

vs

 var a = {x: 1} var o = Object.create(a); ox; // 1 ay = 2; oy; // 2 

La implementación de Sergio Carneiro y Jon requiere que defina una function de initialization para todas las classs less una. Aquí hay una versión modificada de la function de agregación, que utiliza en su lugar parameters pnetworkingeterminados 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 function de agregación preferirá las properties y methods de una class que aparece más adelante en la list de classs.

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 classs personalizadas, puede encadenarse. pero nosotros tan pronto como desee extender alguna function / class escrita no así, no tendrá la oportunidad de continuar 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

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

class Rectangle extends aggregation (Shape, Colonetworking, 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 la agregación .

Justin Fagnani describe una manera muy limpia (imho) de componer múltiples classs en una, usando el hecho de que en ES2015, las classs se pueden crear con expresiones de class.

Expresiones vs declaraciones

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

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

puedes hacer lo mismo con las classs:

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

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

Usar expresiones de class para crear mixins

Puede usar esto para crear una function que crea dinámicamente una class solo cuando se llama a la function:

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

Lo bueno de esto es que puedes definir toda la class de antemano y solo decidir en qué class debe extenderse cuando llames a la function:

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

Si desea mezclar varias classs juntas, dado que las classs ES6 solo son compatibles con la inheritance individual, debe crear una cadena de classs que contenga todas las classs que desee combinar. Entonces digamos que quieres crear una class 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 class D que se extiende B pero no A, tienes un problema.

Pero con algunos trucos inteligentes usando el hecho de que las classs pueden ser expresiones, puedes resolver esto creando A y B no directamente como classs, sino como fábricas de class (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é classs include en la jerarquía.

¡Ayúdanos a build el futuro!

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

micrófonos

micros (pronunciar: mezclar) es una biblioteca que hace que la inheritance múltiple en Javascript sea muy fácil. Inspirado en la excelente publicación de blog Mixins "reales" con classs de Javascript de Justin Fagnani, los micrófonos intentan crear una biblioteca mínima sobre el concepto de usar expresiones de classs (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 class que se pueden usar directamente para crear instancias de objects y se pueden mezclar con otros mixins en lugar de simplemente con classs.

Well Object.assign te ofrece la posibilidad de hacer algo parecido aunque sea un poco más parecido a la composition con las classs de 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(); 

No he visto esto usado en ninguna parte, pero en realidad es bastante útil. Puede utilizar la function shark(){} lugar de la class, pero hay ventajas de usar la class en su lugar.

Creo que la única diferencia con la inheritance con extend palabra key es que la function no solo se basa en el prototype sino también en el object en sí.

Así que ahora cuando haces un new Shark() el shark creado tiene un método de bite , mientras que solo su prototipo tiene un método de eat

No hay una manera fácil de hacer inheritance de classs múltiples. Sigo la combinación de asociación y inheritance para lograr este tipo de comportamiento.

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

Espero que esto sea útil.

use Mixins para ES6 multiple Herencia.

 let classTwo = Base => class extends Base{ // ClassTwo Code }; class Example extends classTwo(ClassOne) { constructor() { } } 

use extensión con function personalizada para gestionar inheritance múltiple 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 Colonetworking { 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, Colonetworking, ZCoord) {} var rect = new Rectangle(7, 42) rect.z = 1000 rect.color = "networking" console.log(rect.x, rect.y, rect.z, rect.color) 

Aquí hay una manera increíble / realmente horrible de extender múltiples classs. Estoy utilizando un par de funciones que Babel puso en mi código transstackdo. La function crea una nueva class que henetworkinga class1 y class1 henetworkinga class2, y así sucesivamente. Tiene sus problemas, pero es una idea divertida.

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