Héritage multiple de classe ES6

134

J'ai fait la plupart de mes recherches à ce sujet sur BabelJS et sur MDN (qui ne contient aucune information), mais n'hésitez pas à me dire si je n'ai pas été assez prudent en cherchant plus d'informations sur la spécification ES6.

Je me demande si ES6 prend en charge l'héritage multiple de la même manière que les autres langages de type canard. Par exemple, puis-je faire quelque chose comme:

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

étendre plusieurs classes à la nouvelle classe? Si tel est le cas, l'interpréteur préférera-t-il les méthodes / propriétés de ClassTwo à ClassOne?

BTC
la source
4
Ce n'est pas vraiment possible avec le fonctionnement actuel de l'héritage dans js, le plus proche que vous puissiez faire est un mixin
qwertymk
Pouvez-vous fournir une sorte de référence qui indique que ce n'est pas possible dans la nouvelle spécification, et si oui, pouvez-vous en faire une réponse pour que je puisse l'accepter?
BTC
J'ai lu les nouvelles classes ES6 n'ajoutent aucune nouvelle fonctionnalité, ce sont juste du sucre de syntaxe.
Oriol
@Oriol, ce sont du sucre syntaxique, mais je m'étais demandé si ce sucre faisait quelque chose avec plusieurs classes en interne.
BTC

Réponses:

70

Un objet ne peut avoir qu'un seul prototype. L'héritage de deux classes peut être effectué en créant un objet parent sous la forme d'une combinaison de deux prototypes parents.

La syntaxe du sous-classement permet de faire cela dans la déclaration, puisque le côté droit de la extendsclause peut être n'importe quelle expression. Ainsi, vous pouvez écrire une fonction qui combine des prototypes selon les critères de votre choix et appeler cette fonction dans la déclaration de classe.

Pointu
la source
1
Je me suis toujours demandé, existe-t-il un moyen de définir un getter sur le __proto__lien pour transférer la recherche d'accessoires vers le bon objet? J'ai essayé mais je n'ai jamais réussi à le faire fonctionner
qwertymk
3
@qwertymk gardez bien à l'esprit que __proto__lui - même est une fonctionnalité obsolète. Cela reflète le lien interne du prototype, mais ce n'est pas vraiment le lien interne du prototype.
Pointy
donc jamais aucune chance qu'un tel piratage ne fonctionne? core-js a fait quelque chose de similaire avec le support de lowmap en utilisant des getters. L'héritage multiple serait très cool
qwertymk
1
@qwertymk eh bien, je ne peux pas dire avec autorité si c'est définitivement impossible. Personnellement, j'utilise très, très rarement l'héritage en JavaScript. En fait, j'utilise assez rarement des prototypes, d'ailleurs.
Pointy
2
Voici la solution que j'ai trouvée : esdiscuss.org/topic/symbol-for-modifying-property-lookup . Exemple: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. Les méthodes et propriétés du dernier constructeur passées pour new MultiClassavoir la priorité la plus élevée, elles sont simplement mélangées dans le nouveau prototype. Je pense qu'une solution encore meilleure existe si elle est réimplémentée à l'aide de proxys ES6, mais il n'y a pas encore assez de support natif pour cela.
trusktr
89

Vérifiez mon exemple ci-dessous, la superméthode fonctionne comme prévu. L'utilisation de quelques astuces instanceoffonctionne même (la plupart du temps):

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

Imprimera

Test D: étend A, B, C -> en dehors de l'instance de D: vrai
from A -> instance interne de A: true
from B -> instance interne de B: true
from C -> instance interne de C: true
de D -> instance interne de D: true
-
Test E: étend A, C -> en dehors de l'instance de E: vrai
from A -> instance interne de A: true
from C -> instance interne de C: true
from E -> instance intérieure de E: true
-
Test F: étend B -> en dehors de l'instance de F: vrai
from B -> instance interne de B: true
from F -> instance interne de F: true
-
Test G: wraper pour utiliser C seul avec un "nouveau" décorateur, joli format -> instance extérieure de G: true
from C -> instance interne de C: true
-
Test B seul, format moche "new (B (Object))" -> hors instance de B: false, celui-ci échoue
from B -> instance interne de B: true

Lien vers violon

Poelinca Dorin
la source
1
Vous pouvez corriger ce format « laid » de B (Object) en faisant B étendre (B||Object).
Aaron
@Aaron je ne suis pas vraiment sûr de vous suivre sur celui-ci (ou vous me suivez). Si F extends (B||Object)au lieu de F extends B(Object), il étendra le mixin B tel qu'il (en tant que fonction), donc F étendra uniquement le prototype Function par défaut puisque B n'a jamais été exécuté. En utilisant, F extends B(Object)nous exécutons en fait la fonction B et F étendra `` tout '' la fonction B renvoie, dans ce cas, c'est la classe B définie à l'intérieur de la fonction B ... petit hack pour garder le nom de classe correct.
Poelinca Dorin
@Aaron, ce que nous pourrions faire, c'est utiliser les paramètres par défaut de la fonction const B = (B = Object) => class extends B {, puis les utiliser class F extends B() {pour une utilisation plus jolie, mais plus moche, hack Kappa
Poelinca Dorin
const B = (B) => class extends (B||Object) {vous laisserait remplacer inst5 = new (B(Object)); // instance only B, ugly formatpar inst5 = new (B());, ou peut-être que je comprends mal le contexte ...
Aaron
@Aaron oui cela fonctionnerait très bien jusqu'à ce que la console.log('from B -> inside instance of B: ${this instanceof B}');sorcière échoue Right-hand side of 'instanceof' is not an object. L'utilisation const B = (B = Object) => class extends B {comme mentionné précédemment passera le test instanceof et vous fournira également l' inst5 = new (B());utilisation si vous le souhaitez.
Poelinca Dorin
23

L'implémentation de Sergio Carneiro et Jon vous oblige à définir une fonction d'initialisation pour toutes les classes sauf une. Voici une version modifiée de la fonction d'agrégation, qui utilise à la place des paramètres par défaut dans les constructeurs. J'inclus également quelques commentaires de ma part.

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

Voici une petite démo:

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.

Cette fonction d'agrégation préférera les propriétés et les méthodes d'une classe qui apparaissent plus tard dans la liste des classes.

Chong Lip Phang
la source
3
quand j'essaye d'utiliser ceci avec react Component, cela ne fonctionne pas. juste FYI à toute autre personne qui aurait pu le vouloir à cette fin.
r3wt
Cela écrase les variables et les fonctions qui ont le même nom.
Vincent Hoch-Drei
17

Justin Fagnani décrit une manière très propre (à mon humble avis) de composer plusieurs classes en une en utilisant le fait que dans ES2015, les classes peuvent être créées avec des expressions de classe .

Expressions vs déclarations

En gros, tout comme vous pouvez créer une fonction avec une expression:

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

vous pouvez faire la même chose avec les classes:

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

L'expression est évaluée à l'exécution, lorsque le code s'exécute, alors qu'une déclaration est exécutée au préalable.

Utilisation d'expressions de classe pour créer des mixins

Vous pouvez l'utiliser pour créer une fonction qui crée dynamiquement une classe uniquement lorsque la fonction est appelée:

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

Ce qui est cool, c'est que vous pouvez définir la classe entière à l'avance et ne décider que de la classe à étendre au moment où vous appelez la fonction:

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

Si vous souhaitez mélanger plusieurs classes ensemble, étant donné que les classes ES6 ne prennent en charge qu'un seul héritage, vous devez créer une chaîne de classes contenant toutes les classes que vous souhaitez mélanger. Alors disons que vous voulez créer une classe C qui étend à la fois A et B, vous pouvez faire ceci:

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

Le problème, c'est que c'est très statique. Si vous décidez par la suite que vous voulez créer une classe D qui étend B mais pas A, vous avez un problème.

Mais avec une astuce intelligente utilisant le fait que les classes peuvent être des expressions, vous pouvez résoudre ce problème en créant A et B non pas directement en tant que classes, mais en tant que fabriques de classes (en utilisant les fonctions fléchées par souci de concision):

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)

Remarquez que nous ne décidons qu'au dernier moment quelles classes inclure dans la hiérarchie.

Stijn de Witt
la source
8

Ce n'est pas vraiment possible avec le fonctionnement de l'héritage prototypique. Jetons un coup d'œil au fonctionnement des accessoires hérités dans 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

voyons ce qui se passe lorsque vous accédez à un accessoire qui n'existe pas:

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

Vous pouvez utiliser des mixins pour obtenir certaines de ces fonctionnalités, mais vous n'obtiendrez pas de liaison tardive:

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

contre

var a = {x: 1}
var o = Object.create(a);
o.x; // 1
a.y = 2;
o.y; // 2
qwertymk
la source
Accepter la réponse de @ Pointy parce qu'il a parlé du mot-clé extend, qui est le cadre de la question réelle et non des modèles d'héritage, mais merci de votre intérêt!
BTC
2

Je propose ces solutions:

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

usage:

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

Tant que vous faites ces trucs avec vos classes écrites sur mesure, vous pouvez les enchaîner. mais nous dès que vous voulez étendre une fonction / classe écrite pas comme ça - vous n'aurez aucune chance de continuer la boucle.

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

fonctionne pour moi dans le nœud v5.4.1 avec le drapeau --harmony

Maikal
la source
Je ne pense pas que vous ayez besoin d'un drapeau d'harmonie pour le nœud 4x et supérieur.
Umayr
2

utilisez Mixins pour l'héritage multiple ES6.

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

class Example extends classTwo(ClassOne) {
    constructor() {
    }
}
No8
la source
3
l'héritage multiple n'est-il pas censé signifier one class inherits from 2 or more unrelated classes? Ce que montre votre exemple est une classe héritant de 2, mais des classes liées. Il s'agit d'un héritage unique et non d'un héritage multiple.
vlad-ardelean
@ vlad-ardelean En fait, la relation est artificielle, ie. établi dynamiquement en appelant classTwo. Faute d'un véritable concept de classe, JS n'a de toute façon aucun héritage structurel . Offhand, je ne peux pas concevoir un scénario JS où les mixins se comportent différemment de ce à quoi vous vous attendez en les conceptualisant comme MI du véritable monde OO (à part la «super» chaîne définie); peut-être qu'une personne plus informée que moi pourra en fournir une.
collapsar
@collapsar Je pense que vous avez tout à fait raison. JS a un héritage prototypique, ce qui signifie qu'il existe une chaîne de prototypes où chaque prototype de la chaîne a un seul parent. Lorsque vous mélangez tout un tas de classes dans la chaîne de prototypes dans un ordre défini, c'est effectivement la même chose que MI dans le monde OO.
Stijn de Witt
2

Depuis la page es6-features.org/#ClassInheritanceFromExpressions , il est possible d'écrire une fonction d'agrégation pour permettre l'héritage multiple:

class Rectangle étend l'agrégation (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
}

Mais cela est déjà fourni dans les bibliothèques comme l' agrégation .

Sergio Carneiro
la source
1

Eh bien, Object.assign vous donne la possibilité de faire quelque chose de proche, mais un peu plus comme une composition avec des 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();

Je n'ai vu cela utilisé nulle part, mais c'est en fait assez utile. Vous pouvez utiliser à la function shark(){}place de la classe, mais il y a des avantages à utiliser la classe à la place.

Je pense que la seule chose différente avec l'héritage avec extendmot-clé est que la fonction ne vit pas seulement sur prototypel'objet mais aussi sur l'objet lui-même.

Ainsi maintenant quand vous faites new Shark()le sharkcréé a une biteméthode, alors que seul son prototype a une eatméthode

Ced
la source
Cela ne fonctionnera pas. Les méthodes de prototype ne seront pas mélangées et la liaison sera erronée.
jonschlinkert
1

Il n'y a pas de moyen simple de faire l'héritage de plusieurs classes. Je suis la combinaison de l'association et de l'héritage pour réaliser ce genre de comportement.

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

J'espère que cela vous sera utile.

AnandShanbhag
la source
1

Cette solution ES6 a fonctionné pour moi:

multiple-inheritance.js

export function allOf(BaseClass, ...Mixins) {

  function copyProperties(target, source) {
    const allPropertyNames = Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source))

    allPropertyNames.forEach((propertyName) => {
      if (propertyName.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
        return
      Object.defineProperty(target, propertyName, Object.getOwnPropertyDescriptor(source, propertyName))
    })
  }

  class Base extends BaseClass
  {
    constructor (...args) {
      super(...args)

      Mixins.forEach((Mixin) => {
        copyProperties(this, new Mixin(...args))
      })
    }
  }

  Mixins.forEach((mixin) => {
    copyProperties(Base.prototype, Mixin.prototype)
  })

  return Base
}

main.js

import { allOf } from "./multiple-inheritance.js"

class A
{
    constructor(name) {
        this.name = name
    }
    sayA() {
        return this.name
    }
}

class B
{
    constructor(name) {
        this.name = name
    }
    sayB() {
        return this.name
    }
}

class AB extends allOf(A, B)
{
    sayAB() {
        return this.name
    }
}

const ab = new AB("ab")
console.log("ab.sayA() = "+ab.sayA()+", ab.sayB() = "+ab.sayB()+", ab.sayAB() = "+ab.sayAB())

Rendements sur la console du navigateur:

ab.sayA() = ab, ab.sayB() = ab, ab.sayAB() = ab
user2006754
la source
ES6 est JavaScript!
Bergi
1

J'ai passé une demi-semaine à essayer de comprendre cela moi-même et j'ai écrit un article complet à ce sujet, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS , et j'espère que cela aidera certains d'entre vous.

En bref, voici comment MI peut être implémenté 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();

Et voici specialize_with () one-liner:

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

Encore une fois, veuillez consulter https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS .

Léonid Titov
la source
1

en javascript, vous ne pouvez pas donner à une classe (fonction constructeur) 2 objets prototypes différents et parce que l'héritage en javascript fonctionne avec un prototype, vous ne pouvez donc pas utiliser plus d'un héritage pour une classe, mais vous pouvez agréger et joindre la propriété de l'objet Prototype et cette propriété principale à l'intérieur d'une classe manuellement avec la refactorisation des classes parentes et ensuite étend cette nouvelle version et la classe jointe à votre classe cible ont du code pour votre question:

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
la source
1

Ma réponse semble être moins de code et cela fonctionne pour moi:

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
la source
0

utiliser l'extension avec une fonction personnalisée pour gérer l'héritage multiple avec 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
la source
0

J'ajouterai également ma solution - je l'ai trouvée la plus conviviale pour moi d'après ce que j'ai lu dans ce fil.

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

Vous pouvez alors l'utiliser comme ceci:

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

Comme preuve de concept, j'ai effectué la fonction suivante. Il prend une liste de classes et les compose dans une nouvelle classe (le dernier prototype gagne donc il n'y a pas de conflits). Lors de la création d'une fonction composée, l'utilisateur peut choisir d'utiliser tous les constructeurs d'origine [ sic! ] ou passer le leur. C'était le plus grand défi de cette expérience: trouver une description de ce que le constructeur devrait faire. La copie de méthodes dans un prototype n'est pas un problème, mais quelle est la logique voulue pour l'objet nouvellement composé. Ou peut-être que cela devrait être sans constructeur? En Python, d'après ce que je sais, il trouve le constructeur correspondant, mais les fonctions dans JS sont plus acceptables, par conséquent, on peut passer à une fonction à peu près tout et à partir de la signature, ce ne sera pas clair.

Je ne pense pas que ce soit optimisé mais le but était d'explorer les possibilités. instanceofne se comportera pas comme prévu, ce qui, je suppose, est une déception, car les développeurs axés sur les classes aiment l'utiliser comme un outil.

Peut-être que JavaScript ne l'a tout simplement pas.

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

Publié à l' origine ici (gist.github.com).

Le témoin
la source
-3

Voici une façon géniale / vraiment merdique d'étendre plusieurs classes. J'utilise quelques fonctions que Babel a mises dans mon code transpilé. La fonction crée une nouvelle classe qui hérite de classe1, et classe1 hérite de classe2, et ainsi de suite. Il a ses problèmes, mais une idée amusante.

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
la source