objeto - MĂșltiples herencia/prototipos en JavaScript
javascript prototype constructor (14)
Llegué a un punto en el que necesito tener algún tipo de herencia múltiple rudimentaria en JavaScript. (No estoy aquí para discutir si esta es una buena idea o no, así que tenga la amabilidad de guardar esos comentarios para usted).
Solo quiero saber si alguien ha intentado esto con éxito (o no) y cómo lo hicieron.
Para reducirlo, lo que realmente necesito es poder tener un objeto capaz de heredar una propiedad de más de una cadena de prototipos (es decir, cada prototipo podría tener su propia cadena), pero en un orden de precedencia dado (lo hará). busca las cadenas para la primera definición).
Para demostrar cómo esto es teóricamente posible, se podría lograr uniendo la cadena secundaria al final de la cadena primaria, pero esto afectaría todas las instancias de cualquiera de esos prototipos anteriores y eso no es lo que quiero.
¿Pensamientos?
Editar Aprecio a las personas de las respuestas, pero aunque el consenso parece estar copiando estáticamente sobre las propiedades de ambos árboles, lo que funcionaría en la mayoría de los casos (y probablemente sea lo que termino haciendo), estaba más interesado en una solución dinámica que permite que las cadenas prototipo separadas sean alteradas, y aún así esos cambios son "recogidos" por la instancia.
Aquí hay un ejemplo de encadenamiento prototipo usando funciones de constructor :
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
Este concepto usa la definición de Yehuda Katz de una "clase" para JavaScript:
... una "clase" JavaScript es solo un objeto Function que sirve como un constructor más un prototipo adjunto. ( Fuente: Guru Katz )
A diferencia del enfoque Object.create , cuando las clases se crean de esta manera y queremos crear instancias de una "clase", no necesitamos saber de qué está heredando cada "clase". Solo usamos 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
El orden de la precedencia debe tener sentido. Primero mira en el objeto instancia, luego es prototipo, luego el siguiente prototipo, 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);
También podemos modificar los prototipos que afectarán a todos los objetos creados en la clase.
// All cats are mutated to be non-mammals
Cat.prototype.isMammal = false;
console.log(tiger, alienCat);
Originalmente escribí algo de esto con esta respuesta .
Creo que es ridículamente simple. El problema aquí es que la clase infantil solo se referirá a instanceof
para la primera clase que llame
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
Eche un vistazo al paquete IeUnit .
La asimilación de conceptos implementada en IeUnit parece ofrecer lo que está buscando de una manera bastante dinámica.
Es posible implementar herencia múltiple en JavaScript, aunque muy pocas bibliotecas lo hacen.
Podría señalar a Ring.js , el único ejemplo que conozco.
Este usa Object.create
para hacer una verdadera cadena de prototipos:
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;
}
Por ejemplo:
var obj = makeChain([{a:1}, {a: 2, b: 3}, {c: 4}]);
volverá:
a: 1
a: 2
b: 3
c: 4
<Object.prototype stuff>
de modo que obj.a === 1
, obj.b === 3
, etc.
Estuve trabajando mucho en esto hoy e intento lograrlo en ES6. La forma en que lo hice fue usando Browserify, Babel y luego lo probé con Wallaby y pareció funcionar. Mi objetivo es ampliar la matriz actual, incluir ES6, ES7 y agregar algunas características personalizadas adicionales que necesito en el prototipo para manejar datos de audio.
Wallaby pasa 4 de mis pruebas. El archivo example.js se puede pegar en la consola y se puede ver que la propiedad ''includes'' está en el prototipo de la clase. Todavía quiero probar esto más mañana.
Este es mi método: (lo más probable es que refactorice y vuelva a empaquetar como un módulo después de dormir un poco).
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
Herencia múltiple [editar, no herencia propia de tipo, sino de propiedades; mixins] en Javascript es bastante sencillo si utiliza prototipos construidos en lugar de objetos genéricos. Aquí hay dos clases principales para heredar de:
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();
Tenga en cuenta que he usado el mismo miembro de "nombre" en cada caso, lo que podría ser un problema si los padres no estaban de acuerdo sobre cómo se debe manejar el "nombre". Pero son compatibles (redundante, realmente) en este caso.
Ahora solo necesitamos una clase que herede de ambos. La herencia se realiza llamando a la función de constructor (sin usar la nueva palabra clave) para los prototipos y los constructores de objetos. Primero, el prototipo debe heredar de los prototipos originales
function FoodPlantPrototype() {
FoodPrototype.call(this);
PlantPrototype.call(this);
// plus a function of its own
this.harvest = function () {
console.log("harvest at", this.maturity);
};
}
Y el constructor tiene que heredar de los constructores padres:
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();
Ahora puedes cultivar, comer y cosechar diferentes instancias:
var fp1 = new FoodPlant(''Radish'', 28);
var fp2 = new FoodPlant(''Corn'', 90);
fp1.grow();
fp2.grow();
fp1.harvest();
fp1.eat();
fp2.harvest();
fp2.eat();
Me gusta la implementación de la estructura de clases de John Resig: http://ejohn.org/blog/simple-javascript-inheritance/
Esto puede simplemente extenderse a algo como:
Class.extend = function(prop /*, prop, prop, prop */) {
for( var i=1, l=arguments.length; i<l; i++ ){
prop = $.extend( prop, arguments[i] );
}
// same code
}
que le permitirá pasar múltiples objetos de los cuales heredar. Vas a perder la capacidad de instanceOf
aquí, pero eso es un hecho si quieres herencia múltiple.
mi ejemplo bastante intrincado de lo anterior está disponible en https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js
Tenga en cuenta que hay algún código muerto en ese archivo, pero permite la herencia múltiple si desea echar un vistazo.
Si desea herencia encadenada (NO herencia múltiple, pero para la mayoría de las personas es lo mismo), se puede lograr con Class como:
var newClass = Class.extend( cls1 ).extend( cls2 ).extend( cls3 )
que preservará la cadena de prototipos original, pero también tendrá un montón de código sin sentido en ejecución.
No se confunda con las implementaciones de frameworks de JavaScript de herencia múltiple.
Todo lo que necesita hacer es usar Object.create() para crear un nuevo objeto cada vez con el objeto y las propiedades prototipo especificados, luego asegúrese de cambiar Object.prototype.constructor cada paso del camino si planea crear una instancia de B
en el futuro.
Para heredar las propiedades de instancia thisA
y thisB
utilizamos Function.prototype.call() al final de cada función de objeto. Esto es opcional si solo te importa heredar el prototipo.
Ejecute el siguiente código en alguna parte y observe 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
hereda el prototipo deA
-
C
hereda el prototipo deB
-
objC
es una instancia deC
Esta es una buena explicación de los pasos anteriores:
No soy de ninguna manera un experto en javascript OOP, pero si te entiendo correctamente quieres algo como (pseudo-código):
Earth.shape = ''round'';
Animal.shape = ''random'';
Cat inherit from (Earth, Animal);
Cat.shape = ''random'' or ''round'' depending on inheritance order;
En ese caso, probaría algo como:
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]());
}
}
Se puede lograr la herencia múltiple en ECMAScript 6 mediante el uso de objetos Proxy .
Implementación
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,
}));
}
Explicación
Un objeto proxy consiste en un objeto objetivo y algunas trampas, que definen el comportamiento personalizado para las operaciones fundamentales.
Al crear un objeto que hereda de otro, usamos Object.create(obj)
. Pero en este caso queremos herencia múltiple, así que en lugar de obj
uso un proxy que redireccionará las operaciones fundamentales al objeto apropiado.
Yo uso estas trampas:
- The
has
trap es una trampa para el operadorin
. Usosome
para verificar si al menos un prototipo contiene la propiedad. - La trampa de
get
es una trampa para obtener valores de propiedad. Utilizofind
para encontrar el primer prototipo que contiene esa propiedad, y devuelvo el valor, o llamo al getter en el receptor apropiado. Esto es manejado porReflect.get
. Si ningún prototipo contiene la propiedad, devuelvoundefined
. - La trampa
set
es una trampa para establecer valores de propiedad. Utilizofind
para encontrar el primer prototipo que contiene esa propiedad, y llamo a su setter en el receptor apropiado. Si no hay incubadora o ningún prototipo contiene la propiedad, el valor se define en el receptor apropiado. Esto es manejado porReflect.set
. - La trampa de
enumerate
es una trampafor...in
loops . Intento iterar las propiedades enumerables del primer prototipo, luego del segundo, y así sucesivamente. Una vez que se ha iterado una propiedad, la almaceno en una tabla hash para evitar repetirla nuevamente.
Advertencia : esta trampa se ha eliminado en el borrador de ES7 y está en desuso en los navegadores. - La trampa
ownKeys
es una trampa paraObject.getOwnPropertyNames()
. Desde ES7,for...in
loops sigue llamando a [[GetPrototypeOf]] y obteniendo las propiedades propias de cada uno. Entonces, para hacer que itere las propiedades de todos los prototipos, uso esta trampa para hacer que todas las propiedades heredadas enumerables aparezcan como propiedades propias. - La trampa
getOwnPropertyDescriptor
es una trampa paraObject.getOwnPropertyDescriptor()
. Hacer que todas las propiedades enumerables aparezcan como propiedades propias en la trampa deownKeys
no es suficiente,for...in
bucles se obtendrá el descriptor para verificar si son enumerables. Así que usofind
para encontrar el primer prototipo que contiene esa propiedad, e itero su cadena prototípica hasta que encuentre el dueño de la propiedad, y devuelvo su descriptor. Si ningún prototipo contiene la propiedad, devuelvoundefined
. El descriptor se modifica para hacerlo configurable, de lo contrario podríamos romper algunas invariantes de proxy. - Las
preventExtensions
ydefineProperty
solo se incluyen para evitar que estas operaciones modifiquen el destino del proxy. De lo contrario, podríamos terminar rompiendo algunas invariantes proxy.
Hay más trampas disponibles, que no uso
- Se podría agregar la trampa
getPrototypeOf
, pero no hay una forma adecuada de devolver los múltiples prototipos. Esto implica queinstanceof
no funcionará. Por lo tanto, dejo que obtenga el prototipo del objetivo, que inicialmente es nulo. - La trampa
setPrototypeOf
podría agregarse y aceptar una matriz de objetos, que reemplazaría a los prototipos. Esto se deja como un ejercicio para el lector. Aquí solo dejo que modifique el prototipo del objetivo, que no es muy útil porque ninguna trampa usa el objetivo. - La trampa
deleteProperty
es una trampa para eliminar propiedades propias. El proxy representa la herencia, por lo que no tendría mucho sentido. Dejo que intente la eliminación en el objetivo, que no debería tener ninguna propiedad de todos modos. - La trampa
isExtensible
es una trampa para obtener la extensibilidad. No es muy útil, dado que un invariante lo obliga a devolver la misma extensibilidad que el objetivo. Así que simplemente dejo que redirija la operación al objetivo, que será extensible. - Las trampas de
apply
yconstruct
son trampas para invocar o crear instancias. Solo son útiles cuando el objetivo es una función o un constructor.
Ejemplo
// 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"
Un recién llegado en la escena es SimpleDeclare . Sin embargo, cuando se trata de herencia múltiple, aún se obtendrán copias de los constructores originales. Esa es una necesidad en Javascript ...
Merc.
Yo usaría ds.oop . Es similar a prototype.js y otros. hace que la herencia múltiple sea muy fácil y minimalista. (solo 2 o 3 kb) También es compatible con algunas características interesantes, como interfaces e inyección de dependencias
/*** 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();
Mixins se puede utilizar en javascript para lograr el mismo objetivo que probablemente desee resolver a través de herencia múltiple en este momento.