Herencia múltiple de clase ES6


134

He realizado la mayor parte de mi investigación sobre esto en BabelJS y en MDN (que no tiene ninguna información), pero no dude en decirme si no he sido lo suficientemente cuidadoso para buscar más información sobre la especificación ES6.

Me pregunto si ES6 admite o no la herencia múltiple de la misma manera que otros lenguajes de tipo pato. Por ejemplo, ¿puedo hacer algo como:

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

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


44
Esto no es realmente posible con la actual forma de herencia trabaja en js, el más cercano que puede hacer es un mixin
qwertymk

¿Puede proporcionar algún tipo de referencia que establezca que esto no es posible en la nueva especificación, y si es así, puede responderlo para que pueda aceptarlo?
BTC

Leí que las nuevas clases de ES6 no agregan ninguna funcionalidad nueva, son solo azúcar de sintaxis.
Oriol


@ Oriol, son azúcar de sintaxis, pero me preguntaba si ese azúcar estaba haciendo algo con varias clases internamente.
BTC

Respuestas:


70

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

La sintaxis para la subclasificación permite hacer eso en la declaración, ya que el lado derecho de la extendsclá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 llamar a esa función en la declaración de clase.


1
Siempre me pregunté, ¿hay alguna forma de establecer un captador en el __proto__enlace para reenviar la búsqueda de utilería al objeto correcto? Lo he intentado pero nunca lo he conseguido
qwertymk

3
@qwertymk, tenga en cuenta que en __proto__sí mismo es una característica obsoleta. Refleja el enlace prototipo interno, pero no es realmente el enlace prototipo interno.
Puntiagudo

entonces, ¿alguna posibilidad de que algún truco como ese funcione? core-js hizo algo similar con el soporte de mapas débiles utilizando getters. La herencia múltiple sería genial
qwertymk

1
@qwertymk bueno, no puedo decir con autoridad si es definitivamente imposible. Personalmente, uso la herencia en JavaScript muy, muy raramente. De hecho, uso prototipos muy raramente, para el caso.
Puntiagudo

2
Aquí está la solución que se me ocurrió: esdiscuss.org/topic/symbol-for-modifying-property-lookup . Muestra: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. Los métodos y propiedades del último constructor pasado para new MultiClasstener mayor prioridad, simplemente se mezclan con el nuevo prototipo. Creo que existe una solución aún mejor si se vuelve a implementar usando Proxies ES6, pero todavía no hay suficiente soporte nativo para ello.
trusktr

89

Consulte mi ejemplo a continuación, el supermétodo funciona como se esperaba. Usar algunos trucos incluso instanceoffunciona (la mayoría de las veces):

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

Imprimirá

Prueba D: extiende A, B, C -> fuera de la instancia 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 C solo 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))" -> fuera de la instancia de B: falso, este falla
de B -> instancia interna de B: verdadero

Enlace para violín


1
Puede arreglar ese "formato feo" de B (Objeto) haciendo que B se extienda (B||Object).
Aaron

@ Aaron no estoy realmente seguro de que te estoy siguiendo en este caso (o me estás siguiendo). Si, en F extends (B||Object)lugar de F extends B(Object)hacerlo, extenderá el mixin B como lo hace (como una función) de modo que F solo extenderá el prototipo de Función predeterminado ya que B nunca se ejecutó. Al usarlo F extends B(Object), en realidad estamos ejecutando la función B y F extenderá 'lo que sea' que devuelva la función B, en este caso es la clase B definida dentro de la función B ... pequeño truco para mantener el nombre correcto de la clase.
Poelinca Dorin

@Aaron lo que podríamos hacer es usar los parámetros predeterminados de la función const B = (B = Object) => class extends B {y luego usarlos class F extends B() {para un uso más bonito, pero el hack más feo Kappa
Poelinca Dorin

const B = (B) => class extends (B||Object) {te dejaría reemplazar inst5 = new (B(Object)); // instance only B, ugly formatcon inst5 = new (B());, o tal vez no entiendo el contexto ...
Aaron

@ Aaron, eso funcionaría bien hasta que la console.log('from B -> inside instance of B: ${this instanceof B}');bruja falle como Right-hand side of 'instanceof' is not an object. El uso const B = (B = Object) => class extends B {como se mencionó anteriormente pasará la instancia de prueba y también le proporcionará el inst5 = new (B());uso si lo desea.
Poelinca Dorin

23

La implementación de Sergio Carneiro y Jon requiere que defina una función inicializadora para todas las clases menos una. Aquí hay una versión modificada de la función de agregación, que utiliza parámetros predeterminados en los constructores. También se incluyen 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 aparecen más adelante en la lista de clases.


3
cuando trato de usar esto con react Component, no funciona. solo para tu información a cualquiera que lo haya querido para este propósito.
r3wt

Eso sobrescribe variables y funciones que tienen el mismo nombre.
Vincent Hoch-Drei

17

Justin Fagnani describe una forma muy limpia (en mi humilde opinión) de componer varias clases en una utilizando el hecho de que en ES2015, las clases se pueden crear con expresiones de clase .

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.

Usando 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 se extenderá para cuando llames a la función:

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

Si desea mezclar varias clases, ya que las clases de ES6 solo admiten herencia única, debe crear una cadena de clases que contenga todas las clases que desea mezclar. Entonces, digamos que desea crear una clase C que extienda tanto A como B, puede 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 decide que desea crear una clase D que se extienda B pero no A, tiene un problema.

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

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.


8

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é sucede 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 obtener parte de esa funcionalidad, pero no obtendrá un enlace tardío:

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

Aceptando la respuesta de @ Pointy porque habló sobre la palabra clave extendidas, que es la pregunta real que se formuló y no los patrones de herencia, ¡pero gracias por interesarse!
BTC

2

Se me ocurrió 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'
            ]
        };
    }
}

Mientras hagas estos trucos con tus clases escritas a medida, puedes encadenarlas. pero tan pronto como quiera extender alguna función / clase escrita de esa manera, no tendrá 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 el indicador --harmony


No creo que necesite una bandera de armonía para el nodo 4x y superior.
Umayr

2

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

class Rectangle extiende la agregación (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 la agregación .


1

use Mixins para la herencia múltiple ES6.

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

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

3
¿no se supone que significa herencia múltiple one class inherits from 2 or more unrelated classes? Lo que muestra su ejemplo es una clase que hereda de 2, pero clases relacionadas. Esta es una herencia única, no una herencia múltiple.
vlad-ardelean

@ vlad-ardelean En realidad, la relación es artificial, es decir. establecido dinámicamente llamando classTwo. Al carecer de un concepto de clase genuino, JS no tiene herencia estructural de todos modos. De improviso, no puedo concebir un escenario JS en el que los mixins se comporten de manera diferente de lo que cabría esperar conceptualizándolos como MI del verdadero mundo OO (aparte de la 'super' cadena definida); Tal vez alguna persona con más conocimientos que yo pueda suministrar uno.
collapsar el

@collapsar Creo que tienes toda la razón. JS tiene herencia prototípica, lo que significa que hay una cadena de prototipos donde cada prototipo en la cadena tiene un solo padre. Cuando se mezclan muchas clases en la cadena de prototipos en un orden definido, es efectivamente lo mismo que MI en el mundo OO.
Stijn de Witt

1

Bueno, Object.assign te da la posibilidad de hacer algo cercano, aunque un poco más como composición con clases 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 ningún lado, pero en realidad es bastante útil. Puede usar en function shark(){}lugar de class pero hay ventajas de usar class en su lugar.

Creo que lo único diferente con la herencia con extendpalabra clave es que la función no vive solo en prototypeel objeto sino también en él.

Por lo tanto, ahora, cuando lo hace, new Shark()lo sharkcreado tiene un bitemétodo, mientras que solo su prototipo tiene un eatmétodo


Esto no funcionara. Los métodos de prototipo no se mezclarán y la unión será incorrecta.
jonschlinkert

1

No hay una manera fácil de hacer una herencia de clases múltiples. Sigo la combinación de asociación y herencia 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.


1

Esta solución ES6 funcionó para mí:

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

Rendimientos en la consola del navegador:

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

¡ES6 es JavaScript!
Bergi

1

Pasé media semana tratando de resolver esto por mí mismo, y escribí un artículo completo sobre él, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS , y espero que ayude a algunos de ustedes.

En resumen, así es como se puede implementar MI en 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();

Y aquí está specialize_with () one-liner:

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

Nuevamente, mire https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS .


1

en javascript no puede dar a una clase (función de constructor) 2 objetos prototipo diferentes y debido a que la herencia en javascript funciona con prototipo, entonces no puede usar más de 1 herencia para una clase, pero puede agregar y unir la propiedad del objeto Prototype y esa propiedad principal dentro de una clase manualmente con la refactorización de las clases principales y luego se extiende esa nueva versión y la clase unida a su clase de destino tiene código para su pregunta:

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

Mi respuesta parece menos código y funciona para mí:

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

use la extensión con una función personalizada para manejar la herencia 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 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

También agregaré mi solución: me pareció la más amigable para mí por lo que leí en este hilo.

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

Puedes usarlo así así:

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

0

Como prueba de concepto, hice la siguiente función. Toma una lista de clases y las compone en una nueva clase (el último prototipo gana para que no haya conflictos). Al crear una función compuesta, el usuario puede elegir usar todos los constructores originales [ sic! ] o pasar el suyo. Este fue el mayor desafío de este experimento: llegar a una descripción de lo que el constructor debería hacer. Copiar métodos en un prototipo no es un problema, pero cuál es la lógica prevista del objeto recién compuesto. ¿O tal vez debería ser sin constructor? En Python, por lo que sé, encuentra el constructor coincidente, pero las funciones en JS son más aceptables, por lo tanto, uno puede pasar a una función casi todo y desde la firma no estará claro.

No creo que esté optimizado, pero el propósito era explorar posibilidades. instanceofno se comportará como se esperaba, lo que, supongo, es un fastidio, ya que a los desarrolladores orientados a la clase les gusta usar esto como herramienta.

Quizás JavaScript simplemente no lo tenga.

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

Publicado originalmente aquí (gist.github.com).



-3

Aquí hay una manera increíble / realmente horrible de extender múltiples clases. Estoy utilizando un par de funciones que Babel puso en mi código transpilado. La función crea una nueva clase que hereda class1, y class1 hereda 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) {}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.