web-dev-qa-db-fra.com

Héritage multiple/prototypes en JavaScript

Je suis arrivé à un point où j'ai besoin d'une sorte d'héritage multiple rudimentaire se déroulant en JavaScript. (Je ne suis pas ici pour discuter de la question de savoir si c'est une bonne idée ou non, alors s'il vous plaît veuillez garder ces commentaires pour vous.)

Je veux juste savoir si quelqu'un a tenté cela avec un succès (ou pas) et comment ils s'y sont pris.

Pour le résumer, ce dont j'ai vraiment besoin, c'est d'avoir un objet capable d'hériter d'une propriété de plus d'un prototype chaîne (c'est-à-dire que chaque prototype pourrait avoir sa propre chaîne), mais dans un ordre donné de priorité (il recherchera les chaînes dans l’ordre de la première définition).

Pour démontrer comment cela est théoriquement possible, vous pouvez y parvenir en attachant la chaîne secondaire à l'extrémité de la chaîne primaire, mais cela affecterait toutes les occurrences de l'un de ces prototypes précédents et ce n'est pas ce que je souhaite.

Pensées?

116
devios1

Mixins peut être utilisé en javascript pour atteindre le même objectif que vous souhaitez probablement résoudre via un héritage multiple pour le moment.

53
Jan Jongboom

L'héritage multiple peut être réalisé dans ECMAScript 6 en utilisant objets proxy .

La mise en oeuvre

function getDesc (obj, prop) {
  var desc = Object.getOwnPropertyDescriptor(obj, prop);
  return desc || (obj=Object.getPrototypeOf(obj) ? getDesc(obj, prop) : void 0);
}
function multiInherit (...protos) {
  return Object.create(new Proxy(Object.create(null), {
    has: (target, prop) => protos.some(obj => prop in obj),
    get (target, prop, receiver) {
      var obj = protos.find(obj => prop in obj);
      return obj ? Reflect.get(obj, prop, receiver) : void 0;
    },
    set (target, prop, value, receiver) {
      var obj = protos.find(obj => prop in obj);
      return Reflect.set(obj || Object.create(null), prop, value, receiver);
    },
    *enumerate (target) { yield* this.ownKeys(target); },
    ownKeys(target) {
      var hash = Object.create(null);
      for(var obj of protos) for(var p in obj) if(!hash[p]) hash[p] = true;
      return Object.getOwnPropertyNames(hash);
    },
    getOwnPropertyDescriptor(target, prop) {
      var obj = protos.find(obj => prop in obj);
      var desc = obj ? getDesc(obj, prop) : void 0;
      if(desc) desc.configurable = true;
      return desc;
    },
    preventExtensions: (target) => false,
    defineProperty: (target, prop, desc) => false,
  }));
}

Explication

Un objet proxy est composé d'un objet cible et de quelques interruptions, qui définissent un comportement personnalisé pour les opérations fondamentales.

Lors de la création d'un objet qui hérite d'un autre, nous utilisons Object.create(obj). Mais dans ce cas, nous voulons un héritage multiple, donc au lieu de obj, j'utilise un proxy qui redirige les opérations fondamentales vers l'objet approprié.

J'utilise ces pièges:

  • Le has trap est un trap pour l'opérateur in . J'utilise some pour vérifier si au moins un prototype contient la propriété.
  • Le get trap est un piège permettant d'obtenir des valeurs de propriété. J'utilise find _ pour trouver le premier prototype qui contient cette propriété et je renvoie la valeur ou appelle l'appelant sur le destinataire approprié. Ceci est géré par Reflect.get . Si aucun prototype ne contient la propriété, je retourne undefined.
  • Le set trap est un piège permettant de définir des valeurs de propriété. J'utilise find _ pour trouver le premier prototype qui contient cette propriété, et j'appelle son sélecteur sur le récepteur approprié. S'il n'y a pas de créateur ou si le prototype ne contient pas la propriété, la valeur est définie sur le récepteur approprié. Ceci est géré par Reflect.set .
  • Le enumerate trap est un piège pour for...in boucles . J'itère les propriétés énumérables du premier prototype, puis du second, et ainsi de suite. Une fois qu'une propriété a été itérée, je la stocke dans une table de hachage pour éviter de la réitérer.
    Warning: cette interruption a été supprimée du brouillon ES7 et est déconseillée dans les navigateurs.
  • Le ownKeys trap est un piège pour Object.getOwnPropertyNames() . Depuis ES7, les boucles for...in continuent d'appeler [[GetPrototypeOf]] et d'obtenir les propriétés propres de chacune d'elles. Donc, afin de le faire itérer les propriétés de tous les prototypes, j'utilise ce piège pour faire en sorte que toutes les propriétés héritées énumérables apparaissent comme des propriétés propres.
  • Le getOwnPropertyDescriptor trap est un piège pour Object.getOwnPropertyDescriptor() . Faire en sorte que toutes les propriétés énumérables apparaissent comme des propriétés propres dans le piège ownKeys n'est pas suffisant, les boucles for...in obtiendront le descripteur pour vérifier si elles sont énumérables. J'utilise donc find pour trouver le premier prototype qui contient cette propriété, et j'itère sa chaîne de prototypes jusqu'à ce que je trouve le propriétaire et que je renvoie son descripteur. Si aucun prototype ne contient la propriété, je retourne undefined. Le descripteur est modifié pour le rendre configurable, sinon nous pourrions casser certains invariants de proxy.
  • Les interruptions preventExtensions _ et defineProperty _ sont uniquement incluses pour empêcher ces opérations de modifier la cible du proxy. Sinon, nous pourrions casser des invariants de proxy.

Il y a plus de pièges disponibles, que je n'utilise pas

  • Le getPrototypeOf trap pourrait être ajouté, mais il n’existe aucun moyen de renvoyer les multiples prototypes. Cela implique que instanceof ne fonctionnera pas non plus. Par conséquent, je le laisse obtenir le prototype de la cible, qui est initialement nul.
  • Le setPrototypeOf trap pourrait être ajouté et accepter un tableau d'objets, qui remplaceraient les prototypes. Ceci est laissé comme un exercice pour le lecteur. Ici, je le laisse simplement modifier le prototype de la cible, ce qui n’est pas très utile car aucun piège n’utilise la cible.
  • La deleteProperty trap est une interruption permettant de supprimer ses propres propriétés. Le proxy représente l'héritage, ce qui n'aurait pas beaucoup de sens. Je le laisse tenter la suppression sur la cible, qui ne devrait avoir aucune propriété de toute façon.
  • Le isExtensible trap est un piège permettant d'obtenir l'extensibilité. Pas très utile, étant donné qu'un invariant l'oblige à retourner la même extensibilité que la cible. Donc, je le laisse simplement rediriger l'opération vers la cible, ce qui sera extensible.
  • Les interruptions apply _ et construct _ sont des interruptions permettant d'appeler ou d'instancier. Ils ne sont utiles que lorsque la cible est une fonction ou un constructeur.

Exemple

// Creating objects
var o1, o2, o3,
    obj = multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3});

// Checking property existences
'a' in obj; // true   (inherited from o1)
'b' in obj; // true   (inherited from o2)
'c' in obj; // false  (not found)

// Setting properties
obj.c = 3;

// Reading properties
obj.a; // 1           (inherited from o1)
obj.b; // 2           (inherited from o2)
obj.c; // 3           (own property)
obj.d; // undefined   (not found)

// The inheritance is "live"
obj.a; // 1           (inherited from o1)
delete o1.a;
obj.a; // 3           (inherited from o3)

// Property enumeration
for(var p in obj) p; // "c", "b", "a"
34
Oriol

Mise à jour (2019): Le message d'origine est en train de devenir obsolète. Cet article et sa bibliothèque GitHub associée constituent une bonne approche moderne.

Message d'origine: Héritage multiple [edit, pas l'héritage approprié de type, mais de propriétés; mixins] en Javascript est assez simple si vous utilisez des prototypes construits plutôt que des objets génériques. Voici deux classes parentes à hériter:

function FoodPrototype() {
    this.eat = function () {
        console.log("Eating", this.name);
    };
}
function Food(name) {
    this.name = name;
}
Food.prototype = new FoodPrototype();


function PlantPrototype() {
    this.grow = function () {
        console.log("Growing", this.name);
    };
}
function Plant(name) {
    this.name = name;
}
Plant.prototype = new PlantPrototype();

Notez que j’ai utilisé le même membre "name" dans chaque cas, ce qui pourrait poser problème si les parents ne sont pas d’accord sur la façon de traiter "name". Mais ils sont compatibles (redondants, vraiment) dans ce cas.

Maintenant, nous avons juste besoin d’une classe qui hérite des deux. L'héritage est effectué en appelant la fonction constructeur (sans utiliser le nouveau mot-clé) pour les prototypes et les constructeurs d'objet. Tout d'abord, le prototype doit hériter des prototypes parents

function FoodPlantPrototype() {
    FoodPrototype.call(this);
    PlantPrototype.call(this);
    // plus a function of its own
    this.harvest = function () {
        console.log("harvest at", this.maturity);
    };
}

Et le constructeur doit hériter des constructeurs parents:

function FoodPlant(name, maturity) {
    Food.call(this, name);
    Plant.call(this, name);
    // plus a property of its own
    this.maturity = maturity;
}

FoodPlant.prototype = new FoodPlantPrototype();

Maintenant, vous pouvez cultiver, manger et récolter différentes instances:

var fp1 = new FoodPlant('Radish', 28);
var fp2 = new FoodPlant('Corn', 90);

fp1.grow();
fp2.grow();
fp1.harvest();
fp1.eat();
fp2.harvest();
fp2.eat();
11
Roy J

Celui-ci utilise Object.create pour créer un véritable prototype de chaîne:

function makeChain(chains) {
  var c = Object.prototype;

  while(chains.length) {
    c = Object.create(c);
    $.extend(c, chains.pop()); // some function that does mixin
  }

  return c;
}

Par exemple:

var obj = makeChain([{a:1}, {a: 2, b: 3}, {c: 4}]);

retournera:

a: 1
  a: 2
  b: 3
    c: 4
      <Object.prototype stuff>

pour que obj.a === 1, obj.b === 3, etc.

5
pimvdb

J'aime l'implémentation par John Resig d'une structure de classe: http://ejohn.org/blog/simple-javascript-inheritance/

Cela peut être simplement étendu à quelque chose comme:

Class.extend = function(prop /*, prop, prop, prop */) {
    for( var i=1, l=arguments.length; i<l; i++ ){
        prop = $.extend( prop, arguments[i] );
    }

    // same code
}

ce qui vous permettra de passer en plusieurs objets dont hériter. Vous allez perdre la capacité instanceOf ici, mais c'est une donnée si vous voulez un héritage multiple.


mon exemple assez compliqué de ce qui précède est disponible sur https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js

Notez qu'il y a du code mort dans ce fichier, mais il autorise l'héritage multiple si vous voulez y jeter un coup d'œil.


Si vous voulez un héritage chaîné (PAS un héritage multiple, mais pour la plupart des gens, c'est la même chose), vous pouvez le réaliser avec Class, comme:

var newClass = Class.extend( cls1 ).extend( cls2 ).extend( cls3 )

qui préservera la chaîne de prototypes d'origine, mais vous aurez également beaucoup de code inutile.

5
Mark Kahn

Ne vous trompez pas avec les implémentations d'infrastructure JavaScript d'héritage multiple.

Tout ce que vous avez à faire est d’utiliser Object.create () pour créer un nouvel objet à chaque fois avec le prototype prototype et les propriétés spécifiées, puis assurez-vous de modifier le Object.prototype.constructor à chaque étape du processus. Ainsi, si vous envisagez d’instancier B à l’avenir.

Pour hériter des propriétés d'instance thisA et thisB, nous utilisons Function.prototype.call () à la fin de chaque fonction d'objet. Ceci est facultatif si vous souhaitez uniquement hériter du prototype.

Exécutez le code suivant quelque part et observez objC:

function A() {
  this.thisA = 4; // objC will contain this property
}

A.prototype.a = 2; // objC will contain this property

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function B() {
  this.thisB = 55; // objC will contain this property

  A.call(this);
}

B.prototype.b = 3; // objC will contain this property

C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;

function C() {
  this.thisC = 123; // objC will contain this property

  B.call(this);
}

C.prototype.c = 2; // objC will contain this property

var objC = new C();
  • B hérite du prototype de A
  • C hérite du prototype de B
  • objC est une instance de C

Ceci est une bonne explication des étapes ci-dessus:

OOP En JavaScript: Ce que vous devez savoir

4
Dave

Je pense que c'est ridiculement simple. Le problème ici est que la classe enfant ne fera référence qu'à instanceof pour la première classe que vous appelez.

https://jsfiddle.net/1033xzyt/19/

function Foo() {
  this.bar = 'bar';
  return this;
}
Foo.prototype.test = function(){return 1;}

function Bar() {
  this.bro = 'bro';
  return this;
}
Bar.prototype.test2 = function(){return 2;}

function Cool() {
  Foo.call(this);
  Bar.call(this);

  return this;
}

var combine = Object.create(Foo.prototype);
$.extend(combine, Object.create(Bar.prototype));

Cool.prototype = Object.create(combine);
Cool.prototype.constructor = Cool;

var cool = new Cool();

console.log(cool.test()); // 1
console.log(cool.test2()); //2
console.log(cool.bro) //bro
console.log(cool.bar) //bar
console.log(cool instanceof Foo); //true
console.log(cool instanceof Bar); //false
2
BarryBones41

Je travaillais beaucoup sur cela aujourd'hui et j'essayais d'y parvenir moi-même dans ES6. La façon dont je l’ai utilisée utilisait Browserify, Babel, puis je l’ai testée avec Wallaby et cela a semblé fonctionner. Mon objectif est d'étendre le module Array actuel, d'inclure ES6 et ES7 et d'ajouter certaines fonctionnalités personnalisées supplémentaires dont j'ai besoin dans le prototype pour la gestion des données audio. 

Wallaby passe 4 de mes tests. Le fichier example.js peut être collé dans la console et vous pouvez voir que la propriété "includes" est dans le prototype de la classe. Je veux toujours tester plus demain.

Voici ma méthode: (Je vais probablement refactoriser et reconditionner sous forme de module après un peu de sommeil!)

var includes = require('./polyfills/includes');
var keys =  Object.getOwnPropertyNames(includes.prototype);
keys.shift();

class ArrayIncludesPollyfills extends Array {}

function inherit (...keys) {
  keys.map(function(key){
      ArrayIncludesPollyfills.prototype[key]= includes.prototype[key];
  });
}

inherit(keys);

module.exports = ArrayIncludesPollyfills

Github Repo: https://github.com/danieldram/array-includes-polyfill

2
Daniel Ram

Je ne suis en aucun cas un expert en programmation javascript, mais si je vous ai bien compris, vous voulez quelque chose comme (pseudo-code):

Earth.shape = 'round';
Animal.shape = 'random';

Cat inherit from (Earth, Animal);

Cat.shape = 'random' or 'round' depending on inheritance order;

Dans ce cas, j’essaierais quelque chose comme:

var Earth = function(){};
Earth.prototype.shape = 'round';

var Animal = function(){};
Animal.prototype.shape = 'random';
Animal.prototype.head = true;

var Cat = function(){};

MultiInherit(Cat, Earth, Animal);

console.log(new Cat().shape); // yields "round", since I reversed the inheritance order
console.log(new Cat().head); // true

function MultiInherit() {
    var c = [].shift.call(arguments),
        len = arguments.length
    while(len--) {
        $.extend(c.prototype, new arguments[len]());
    }
}
2
David Hellsing

Il est possible d'implémenter l'héritage multiple en JavaScript, bien que très peu de bibliothèques le fassent.

Je pourrais indiquer Ring.js , le seul exemple que je connaisse.

2
nicolas-van

Je voudrais utiliser ds.oop . Son semblable à prototype.js et autres. rend l'héritage multiple très facile et son minimaliste. (seulement 2 ou 3 kb) Prend également en charge d’autres fonctionnalités telles que les interfaces et l’injection de dépendances

/*** multiple inheritance example ***********************************/

var Runner = ds.class({
    run: function() { console.log('I am running...'); }
});

var Walker = ds.class({
    walk: function() { console.log('I am walking...'); }
});

var Person = ds.class({
    inherits: [Runner, Walker],
    eat: function() { console.log('I am eating...'); }
});

var person = new Person();

person.run();
person.walk();
person.eat();
0
dss

Un retardataire dans la scène est SimpleDeclare . Cependant, en cas d'héritage multiple, vous obtiendrez toujours des copies des constructeurs d'origine. C'est une nécessité en Javascript ...

Merc.

0
Merc

Vérifiez le code ci-dessous qui IS indique la prise en charge de l'héritage multiple. Fait en utilisant HÉRITAGE PROTOTYPAL

function A(name) {
    this.name = name;
}
A.prototype.setName = function (name) {

    this.name = name;
}

function B(age) {
    this.age = age;
}
B.prototype.setAge = function (age) {
    this.age = age;
}

function AB(name, age) {
    A.prototype.setName.call(this, name);
    B.prototype.setAge.call(this, age);
}

AB.prototype = Object.assign({}, Object.create(A.prototype), Object.create(B.prototype));

AB.prototype.toString = function () {
    return `Name: ${this.name} has age: ${this.age}`
}

const a = new A("shivang");
const b = new B(32);
console.log(a.name);
console.log(b.age);
const ab = new AB("indu", 27);
console.log(ab.toString());
0
Shivang Gupta

Voici un exemple de chaînage prototype utilisant des fonctions de constructeur:

function Lifeform () {             // 1st Constructor function
    this.isLifeform = true;
}

function Animal () {               // 2nd Constructor function
    this.isAnimal = true;
}
Animal.prototype = new Lifeform(); // Animal is a lifeform

function Mammal () {               // 3rd Constructor function
    this.isMammal = true;
}
Mammal.prototype = new Animal();   // Mammal is an animal

function Cat (species) {           // 4th Constructor function
    this.isCat = true;
    this.species = species
}
Cat.prototype = new Mammal();     // Cat is a mammal

Ce concept utilise la définition de Yehuda Katz d'une "classe" pour JavaScript:

... une "classe" JavaScript est simplement un objet Function servant de constructeur et d'objet prototype associé. ( Source: Guru Katz )

Contrairement à l'approche Object.create , lorsque les classes sont construites de cette manière et que nous voulons créer des instances d'une "classe", nous n'avons pas besoin de savoir de quoi chaque "classe" hérite. Nous utilisons simplement new.

// Make an instance object of the Cat "Class"
var tiger = new Cat("tiger");

console.log(tiger.isCat, tiger.isMammal, tiger.isAnimal, tiger.isLifeform);
// Outputs: true true true true

L'ordre de préséance devrait avoir un sens. Il cherche d'abord dans l'objet d'instance, puis son prototype, puis le prochain prototype, etc.

// Let's say we have another instance, a special alien cat
var alienCat = new Cat("alien");
// We can define a property for the instance object and that will take 
// precendence over the value in the Mammal class (down the chain)
alienCat.isMammal = false;
// OR maybe all cats are mutated to be non-mammals
Cat.prototype.isMammal = false;
console.log(alienCat);

Nous pouvons également modifier les prototypes qui affecteront tous les objets construits sur la classe.

// All cats are mutated to be non-mammals
Cat.prototype.isMammal = false;
console.log(tiger, alienCat);

Au départ, j'avais écrit ceci avec cette réponse .

0
Luke

Jetez un coup d'oeil au paquet IeUnit

Le concept d’assimilation mis en œuvre dans IeUnit semble offrir ce que vous recherchez de manière assez dynamique.

0
James