J'ai effectué 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 attentif pour plus d’informations sur la ES6 Spec.
Je me demande si ES6 prend en charge l'héritage multiple de la même manière que les autres langages typés. 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ète préférera-t-il les méthodes/propriétés de ClassTwo à ClassOne?
Un objet ne peut avoir qu'un seul prototype. Vous pouvez hériter de deux classes en créant un objet parent en combinant deux prototypes parent.
La syntaxe de sous-classement permet de le faire dans la déclaration, car le membre de droite de la clause extends
peut être n’importe quelle expression. Ainsi, vous pouvez écrire une fonction combinant des prototypes selon les critères de votre choix et appeler cette fonction dans la déclaration de classe.
Vérifiez mon exemple ci-dessous, la méthode super
fonctionne comme prévu. Utiliser quelques astuces même instanceof
fonctionne (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();
Va imprimer
Test D: étend A, B, C -> instance extérieure de D: true De A -> instance interne de A: true De B -> instance interne de B: true De C -> instance interne de C: true De D -> instance interne de D: true - Test E: étend A, C -> instance externe de E: true De A - > instance interne de A: true de C -> instance interne de C: true de E -> instance interne de E: true - Test F: étend B -> instance externe de F: true De B -> instance interne de B: true De F -> instance interne de F: true - Tester G: wraper pour utiliser C seul avec le "nouveau" décorateur, joli format -> instance externe de G: true de C -> instance interne de C: true - Testez B seul, format moche "nouveau (B (Object))" -> instance externe de B: false, celui-ci échoue De B -> instance interne de B: true
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 plutôt les paramètres par défaut dans les constructeurs. Inclus sont aussi quelques commentaires par moi.
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 apparaîtront plus tard dans la liste des classes.
Ce n'est pas vraiment possible avec la manière dont fonctionne l'héritage prototype. Voyons comment fonctionnent les 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 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
Justin Fagnani décrit une manière très simple (imho) de composer plusieurs classes en une seule en utilisant le fait que dans ES2015, les classes peuvent être créées avec la classe expressions.
En gros, 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 au moment de l'exécution, à l'exécution du code, tandis qu'une déclaration est exécutée au préalable.
Vous pouvez utiliser ceci 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
}
}
La bonne chose à ce sujet est que vous pouvez définir la classe entière à l’avance et décider uniquement 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, les classes ES6 ne prenant en charge que l'héritage unique, 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 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 plus tard de créer une classe D qui étend B mais pas A, vous avez un problème.
Mais avec quelques astuces intelligentes utilisant le fait que les classes peuvent être des expressions, vous pouvez résoudre ce problème en créant A et B pas directement en tant que classes, mais en tant que fabriques de classes (en utilisant des fonctions de flèche 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 comme nous ne décidons qu'au dernier moment quelles classes inclure dans la hiérarchie.
Bien sûr, si vous êtes comme moi, cela vous incite à construire la bibliothèque ultime pour l'héritage multiple en Javascript. Si vous le souhaitez, aidez-moi à faire exactement cela! Découvrez ce projet et aidez-vous si vous le pouvez!
mics (prononcer: mix) est une bibliothèque qui simplifie l'héritage multiple en Javascript. Inspiré par l'excellent article de blog «Real» Mixins avec les classes Javascript de Justin Fagnani, mics tente de créer une bibliothèque minimale autour du concept d'utilisation d'expressions de classe (usines) en tant que mixines. mics étend les concepts présentés dans l'article de blog en faisant des mixins citoyens de première classe qui peuvent être utilisés directement pour instancier des objets et peuvent être mélangés avec d'autres mixins plutôt qu'avec des classes uniquement.
Depuis la page es6-features.org/#ClassInheritanceFromExpressions , il est possible d'écrire une fonction d'agrégation pour autoriser 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 des bibliothèques commeaggregation .
Je viens avec 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 ce tour avec vos classes écrites sur mesure, vous pouvez les enchaîner. mais dès que vous voulez étendre une fonction/classe écrite pas comme ça - vous n’aurez plus aucune chance de continuer en 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 noeud v5.4.1 avec --harmony flag
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 pas vu cela utilisé nulle part, mais c'est en fait très utile. Vous pouvez utiliser function shark(){}
au lieu de la classe, mais utiliser la classe présente des avantages.
Je crois que la seule différence avec l'héritage avec le mot clé extend
est que la fonction ne vit pas uniquement sur la variable prototype
, mais également sur l'objet lui-même.
Donc maintenant, quand vous faites new Shark()
, la shark
créée a une méthode bite
, alors que seul son prototype a une méthode eat
utilisez Mixins pour l'héritage multiple ES6.
let classTwo = Base => class extends Base{
// ClassTwo Code
};
class Example extends classTwo(ClassOne) {
constructor() {
}
}
Cette solution ES6 a fonctionné pour moi:
multiple-héritage.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
Il n'y a pas de moyen facile de faire l'héritage de plusieurs classes. Je suis l'association de l'héritage pour associer ce type 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 c'est utile.
ça marche pour moi !! (2019)
class Example extends (ClassOne, ClassTwo)
{
constructor()
{
}
}
Je vais aussi ajouter ma solution - je l’ai trouvée la plus conviviale de 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 l'utiliser ensuite comme ceci:
class _MyBaseClass {}
const MyBaseClass = aggregate(ExtensionOne, ExtensionTwo)(_MyBaseClass);
Pour prouver le concept, j'ai effectué la fonction suivante. Il prend une liste de classes et les compose dans une nouvelle classe (le dernier prototype gagne afin d'éviter tout conflit). Lors de la création d'une fonction composée, l'utilisateur peut choisir d'utiliser tous les constructeurs d'origine [sic!] ou de transmettre leurs propres. C'était le plus gros 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 un 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 matching, mais les fonctions de JS sont plus acceptables. Par conséquent, on peut passer à une fonction à peu près tout et la signature ne sera pas claire.
Je ne pense pas que ce soit optimisé, mais le but était d'explorer les possibilités. instanceof
ne se comportera pas comme prévu, ce qui, je suppose, est une déception, car les développeurs orientés vers 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);
Initialement posté ici (Gist.github.com).
utilisez Extent avec une fonction personnalisée pour gérer plusieurs héritages 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)