Classe ES6 Herança múltipla

134

Eu fiz a maior parte de minha pesquisa sobre isso no BabelJS e no MDN (que não tem nenhuma informação), mas fique à vontade para me dizer se não tomei o cuidado de procurar informações adicionais sobre o ES6 Spec.

Gostaria de saber se o ES6 suporta ou não herança múltipla da mesma maneira que outras linguagens tipadas por patos. Por exemplo, posso fazer algo como:

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

estender várias classes para a nova classe? Nesse caso, o intérprete prefere métodos / propriedades do ClassTwo ao ClassOne?

BTC
fonte
4
Isto não é realmente possível com a atual forma de herança funciona em js, o mais próximo que você pode fazer é um mixin
qwertymk
Você pode fornecer algum tipo de referência que afirme que isso não é possível na nova especificação e, em caso afirmativo, você pode fazer uma resposta para que eu possa aceitá-la?
BTC
Eu li que as novas classes ES6 não adicionam nenhuma nova funcionalidade, elas são apenas açúcar de sintaxe.
229 Oriol
@Oriol, eles são açúcar de sintaxe, mas eu me perguntava se esse açúcar estava fazendo algo com várias classes internamente.
BTC

Respostas:

70

Um objeto pode ter apenas um protótipo. A herança de duas classes pode ser feita criando um objeto pai como uma combinação de dois protótipos pai.

A sintaxe da subclasse torna possível fazer isso na declaração, pois o lado direito da extendscláusula pode ser qualquer expressão. Portanto, você pode escrever uma função que combine protótipos de acordo com os critérios que desejar e chamar essa função na declaração de classe.

Pontudo
fonte
1
Sempre me perguntei: existe alguma maneira de definir um getter no __proto__link para encaminhar a pesquisa de prop para o objeto correto? Eu tentei, mas não já chegou a funcionar
qwertymk
3
@qwertymk lembre-se de que __proto__ele é um recurso obsoleto. Ele reflete o link do protótipo interno, mas não é realmente o link do protótipo interno.
Pointy
então nunca alguma chance de algum hack como esse funcionar? core-js fez algo semelhante com suporte a mapas fracos usando getters. Herança múltipla seria muito legal
qwertymk
1
@qwertymk bem, não posso dizer com autoridade se é definitivamente impossível. Pessoalmente, uso herança em JavaScript muito, muito raramente. Na verdade, eu uso protótipos muito raramente, nesse caso.
Pointy
2
Aqui está a solução que eu encontrei: esdiscuss.org/topic/symbol-for-modifying-property-lookup . Amostra: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. Os métodos e propriedades do último construtor passados ​​para new MultiClasster maior precedência, são apenas misturados ao novo protótipo. Acho que existe uma solução ainda melhor se reimplementada usando os ES6 Proxies, mas ainda não há suporte nativo suficiente para isso.
trusktr
89

Confira meu exemplo abaixo, supermétodo funcionando conforme o esperado. Usar alguns truques instanceoffunciona mesmo (na maioria das vezes):

// 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á

Teste D: estende A, B, C -> instância externa de D: true
de A -> instância interna de A: true
de B -> instância interna de B: true
de C -> instância interna de C: true
de D -> instância interna de D: true
-
Teste E: estende A, C -> instância externa de E: true
de A -> instância interna de A: true
de C -> instância interna de C: true
de E -> instância interna de E: true
-
Teste F: estende B -> instância externa de F: true
de B -> instância interna de B: true
de F -> instância interna de F: true
-
Teste G: wraper para usar C sozinho com o decorador "novo", formato bonito -> instância externa de G: true
de C -> instância interna de C: true
-
Teste B sozinho, formato feio "new (B (Object))" -> instância externa de B: false, esta falha
de B -> instância interna de B: true

Link para mexer

Poelinca Dorin
fonte
1
Você pode corrigir esse "formato feio" de B (Objeto) fazendo B estender (B||Object).
Aaron
@ Aaron, não tenho muita certeza de estar seguindo você neste (ou você está me seguindo). Se, em F extends (B||Object)vez de F extends B(Object), ele estenderá o mixin B como ele (como uma função), de modo que F somente estenderá o protótipo de função padrão, pois B nunca foi executado. Ao usar F extends B(Object), na verdade estamos executando a função B e F estenderá o 'retorno' da função B; nesse caso, é a classe B definida dentro da função B ... pequeno truque para manter o nome da classe correto.
Poelinca Dorin
@ Aaron o que poderíamos fazer é função uso parâmetros padrão const B = (B = Object) => class extends B {e, em seguida, usar class F extends B() {para um uso mais bonita, mas mais feio cortar Kappa
Poelinca Dorin
const B = (B) => class extends (B||Object) {iria deixá-lo substituir inst5 = new (B(Object)); // instance only B, ugly formatcom inst5 = new (B());, ou talvez eu não compreendem o contexto ...
Aaron
@ Aaron sim, isso funcionaria muito bem até que a console.log('from B -> inside instance of B: ${this instanceof B}');bruxa falhe Right-hand side of 'instanceof' is not an object. Usar const B = (B = Object) => class extends B {como mencionado anteriormente passará na instância do teste e fornecerá o inst5 = new (B());uso também, se você desejar.
Poelinca Dorin
23

A implementação de Sergio Carneiro e Jon exige que você defina uma função inicializadora para todas, exceto uma classe. Aqui está uma versão modificada da função de agregação, que utiliza parâmetros padrão nos construtores. Também estão incluídos alguns comentários meus.

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

Aqui está uma pequena demonstração:

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.

Essa função de agregação preferirá propriedades e métodos de uma classe que aparecerão mais tarde na lista de classes.

Chong Lip Phang
fonte
3
quando tento usar isso com o react Component, ele não funciona. apenas para sua informação a qualquer pessoa que possa ter desejado esse objetivo.
R3wt 13/0518
Isso substitui variáveis ​​e funções que têm o mesmo nome.
Vincent Hoch-Drei
17

Justin Fagnani descreve uma maneira muito clara (imho) de compor várias classes em uma usando o fato de que no ES2015, as classes podem ser criadas com expressões de classe .

Expressões vs declarações

Basicamente, assim como você pode criar uma função com uma expressão:

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

você pode fazer o mesmo com as classes:

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

A expressão é avaliada em tempo de execução, quando o código é executado, enquanto uma declaração é executada previamente.

Usando expressões de classe para criar mixins

Você pode usar isso para criar uma função que cria dinamicamente uma classe somente quando a função é chamada:

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

O legal é que você pode definir toda a classe de antemão e apenas decidir em qual classe ela deve se estender quando você chamar a função:

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

Se você deseja misturar várias classes, porque as classes ES6 suportam apenas herança única, é necessário criar uma cadeia de classes que contenha todas as classes que você deseja misturar. Então, digamos que você queira criar uma classe C que estenda A e B, você pode fazer isso:

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

O problema disso é que é muito estático. Se você decidir mais tarde criar uma classe D que estenda B, mas não A, você terá um problema.

Mas com alguns truques inteligentes usando o fato de que as classes podem ser expressões, você pode resolver isso criando A e B não diretamente como classes, mas como fábricas de classes (usando funções de seta por questões de brevidade):

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 como decidimos apenas no último momento quais classes incluir na hierarquia.

Stijn de Witt
fonte
8

Isso não é realmente possível com a maneira como a herança prototípica funciona. Vamos dar uma olhada em como os objetos herdados funcionam em 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

vamos ver o que acontece quando você acessa um suporte que não 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

Você pode usar mixins para obter algumas dessas funcionalidades, mas não obterá ligação tardia:

var a = {x: '1'};
var b = {y: '2'};
var c = createWithMixin([a, b]);
c.x; // 1
c.y; // 2
b.z = 3;
c.z; // undefined

vs

var a = {x: 1}
var o = Object.create(a);
o.x; // 1
a.y = 2;
o.y; // 2
qwertymk
fonte
Aceitando a resposta de @ Pointy porque ele falou sobre a palavra-chave extends, que é a estrutura da pergunta real e não sobre os padrões de herança, mas obrigado por se interessar!
BTC
2

Eu vim com estas soluções:

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

Contanto que você esteja fazendo esses truques com suas classes personalizadas, ele pode ser encadeado. mas nós assim que você quiser estender alguma função / classe escrita assim - você não terá chance de continuar o loop.

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

funciona para mim no nó v5.4.1 com o sinalizador --harmony

Maikal
fonte
Eu não acho que você precise de sinalizador de harmonia para o nó 4x e acima.
Umayr
2

Na página es6-features.org/#ClassInheritanceFromExpressions , é possível escrever uma função de agregação para permitir herança múltipla:

A classe Rectangle estende a agregação (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
}

Mas isso já é fornecido em bibliotecas como agregação .

Sergio Carneiro
fonte
1

use Mixins para herança múltipla do ES6.

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

class Example extends classTwo(ClassOne) {
    constructor() {
    }
}
No8
fonte
3
herança múltipla não deveria significar one class inherits from 2 or more unrelated classes? O que seu exemplo mostra é uma classe herdada de 2, mas classes relacionadas. Isso é herança única, não herança múltipla.
Vlad-Ardelean
@ vlad-ardelean Na verdade, a relação é artificial, ou seja. estabelecido dinamicamente chamando classTwo. Na falta de um conceito de classe genuíno, JS não tem herança estrutural de qualquer maneira. Imediatamente, não consigo conceber um cenário de JS em que os mixins se comportem de maneira diferente do que você esperaria conceituá-los como MI do verdadeiro mundo OO (além da 'super' cadeia definida); talvez uma pessoa com mais conhecimento do que eu possa fornecer uma.
Collapsar
@ collapsar Eu acho que você está absolutamente certo. JS tem herança prototípica, o que significa que há uma cadeia de protótipos em que cada protótipo na cadeia tem um único pai. Ao misturar um monte de classes na cadeia de protótipos em uma ordem definida, é efetivamente o mesmo que o MI no mundo OO.
Stijn de Witt
1

Bem, Object.assign oferece a possibilidade de fazer algo próximo, embora um pouco mais parecido com a composição com as classes 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();

Eu não vi isso usado em nenhum lugar, mas é realmente bastante útil. Você pode usar em function shark(){}vez da classe, mas há vantagens em usar a classe.

Acredito que a única coisa diferente da herança com a extendpalavra-chave é que a função não vive apenas prototypedo objeto, mas também dele.

Assim, agora, quando você faz new Shark()o sharkcriado, tem um bitemétodo, enquanto apenas o protótipo possui um eatmétodo

Ced
fonte
Isso não vai funcionar. Os métodos de protótipo não serão misturados e a ligação estará incorreta.
jonschlinkert
1

Não há uma maneira fácil de fazer herança de várias classes. Sigo a combinação de associação e herança para atingir esse tipo de 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());

Espero que isso seja útil.

AnandShanbhag
fonte
1

Esta solução ES6 funcionou para mim:

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

Rendimentos no console do navegador:

ab.sayA() = ab, ab.sayB() = ab, ab.sayAB() = ab
user2006754
fonte
ES6 é JavaScript!
Bergi
1

Passei meia semana tentando descobrir isso sozinho e escrevi um artigo inteiro sobre isso, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS , e espero que ajude alguns de vocês.

Em resumo, veja como o MI pode ser implementado em 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();

E aqui está specialize_with () one-liner:

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

Novamente, consulte https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS .

Leonid Titov
fonte
1

em javascript, você não pode atribuir a uma classe (função construtora) 2 objetos de protótipo diferentes e porque a herança em javascript trabalha com prototype, portanto você não pode usar mais de uma herança para uma classe, mas é possível agregar e associar propriedades do objeto Prototype e dessa propriedade principal dentro de uma classe manualmente, refatorando as classes pai e, em seguida, estende a nova versão e ingressou na classe à sua classe de destino com o código para sua pergunta:

let Join = (...classList) => {

    class AggregatorClass {

        constructor() {
            classList.forEach((classItem, index) => {

                let propNames = Object.getOwnPropertyNames(classItem.prototype);

                propNames.forEach(name => {
                    if (name !== 'constructor') {
                        AggregatorClass.prototype[name] = classItem.prototype[name];
                    }
                });
            });

            classList.forEach(constructor => {
                Object.assign(AggregatorClass.prototype, new constructor())
            });
        }
    }


    return AggregatorClass

};
saman
fonte
1

Minha resposta parece menos código e funciona para mim:

class Nose {
  constructor() {
    this.booger = 'ready'; 
  }

  pick() {
    console.log('pick your nose')
  } 
}

class Ear {
  constructor() {
    this.wax = 'ready'; 
  }

  dig() {
    console.log('dig in your ear')
  } 
}

class Gross extends Classes([Nose,Ear]) {
  constructor() {
    super();
    this.gross = true;
  }
}

function Classes(bases) {
  class Bases {
    constructor() {
      bases.forEach(base => Object.assign(this, new base()));
    }
  }
  bases.forEach(base => {
    base.prototype
    .properties()
    .filter(prop => prop != 'constructor')
    .forEach(prop => Bases.prototype[prop] = base.prototype[prop])
  })
  return Bases;
}


// test it
function dontLook() {
  var grossMan = new Gross();
  grossMan.pick(); // eww
  grossMan.dig();  // yuck!
}
toddmo
fonte
0

use extensão com função personalizada para lidar com herança múltipla com 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)

Jon
fonte
0

Também adicionarei minha solução - achei a mais amigável para mim pelo que li nesta discussão.

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

Você pode usá-lo assim:

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

Como prova de conceito, fiz a seguinte função. Ele pega uma lista de classes e as compõe em uma nova classe (o último protótipo vence para que não haja conflitos). Ao criar uma função composta, o usuário pode optar por usar todos os construtores originais [ sic! ] ou passar por conta própria. Este foi o maior desafio deste experimento: apresentar uma descrição do que o construtor deve fazer. Copiar métodos para um protótipo não é um problema, mas qual é a lógica pretendida do objeto recém-composto. Ou talvez devesse ser sem construtores? No Python, pelo que sei, ele encontra o construtor de correspondência, mas as funções em JS são mais aceitas, portanto, pode-se passar para uma função praticamente tudo e, por assinatura, não ficará claro.

Não acho que seja otimizado, mas o objetivo era explorar possibilidades. instanceofnão se comportará como esperado, o que, eu acho, é uma chatice, já que os desenvolvedores orientados a classe gostam de usar isso como uma ferramenta.

Talvez o JavaScript simplesmente não o tenha.

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

Originalmente publicado aqui (gist.github.com).

A testemunha
fonte
-3

Aqui está uma maneira incrível / realmente péssima de estender várias classes. Estou utilizando algumas funções que Babel colocou no meu código transpilado. A função cria uma nova classe que herda a classe1, e a classe1 herda a classe2, e assim por diante. Tem seus problemas, mas é uma ideia 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) {}
Casey
fonte