Solución:
Se puede lograr una 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
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) ,
*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 consta de un objeto de destino y algunas trampas, que definen un comportamiento personalizado para 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
Utilizo un proxy que redirigirá las operaciones fundamentales al objeto apropiado.
Yo uso estas trampas:
- los
has
trampa es una trampa para elin
operador. yo suelosome
para comprobar si al menos un prototipo contiene la propiedad. - los
get
trap es una trampa para obtener valores de propiedad. yo suelofind
para encontrar el primer prototipo que contiene esa propiedad, y devuelvo el valor, o llamo al captador en el receptor apropiado. Esto es manejado porReflect.get
. Si ningún prototipo contiene la propiedad, regreso.undefined
. - los
set
trap es una trampa para establecer valores de propiedad. yo suelofind
para encontrar el primer prototipo que contiene esa propiedad, y llamo a su setter en el receptor apropiado. Si no hay setter o ningún prototipo contiene la propiedad, el valor se define en el receptor apropiado. Esto es manejado porReflect.set
. - los
enumerate
trampa es una trampa parafor...in
bucles. Repito las propiedades enumerables del primer prototipo, luego del segundo, y así sucesivamente. Una vez que se ha iterado una propiedad, la guardo en una tabla hash para evitar repetirla nuevamente.
Advertencia: Esta trampa se eliminó en el borrador de ES7 y está obsoleta en los navegadores. - los
ownKeys
trampa es una trampa paraObject.getOwnPropertyNames()
. Desde ES7,for...in
los bucles siguen llamando [[GetPrototypeOf]]y obteniendo las propiedades propias de cada uno. Entonces, para que repita las propiedades de todos los prototipos, utilizo esta trampa para hacer que todas las propiedades heredadas enumerables aparezcan como propiedades propias. - los
getOwnPropertyDescriptor
trampa es una trampa paraObject.getOwnPropertyDescriptor()
. Hacer que todas las propiedades enumerables aparezcan como propiedades propias en elownKeys
la trampa no es suficiente,for...in
los bucles obtendrán el descriptor para comprobar si son enumerables. Entonces usofind
para encontrar el primer prototipo que contiene esa propiedad, e iteraré su cadena prototípica hasta que encuentro al dueño de la propiedad y devuelvo su descriptor. Si ningún prototipo contiene la propiedad, regreso.undefined
. El descriptor se modifica para hacerlo configurable, de lo contrario podríamos romper algunos invariantes de proxy. - los
preventExtensions
ydefineProperty
las trampas solo se incluyen para evitar que estas operaciones modifiquen el destino del proxy. De lo contrario, podríamos terminar rompiendo algunos invariantes de proxy.
Hay más trampas disponibles, que no uso.
- los
getPrototypeOf
Se podría agregar una trampa, pero no hay una forma adecuada de devolver los múltiples prototipos. Esto implicainstanceof
tampoco funcionará. Por lo tanto, dejo que obtenga el prototipo del objetivo, que inicialmente es null. - los
setPrototypeOf
Se podría agregar una trampa y aceptar una array de objetos, que reemplazarían a los prototipos. Esto se deja como ejercicio para el lector. Aquí solo dejo que modifique el prototipo del objetivo, lo cual no es de mucha utilidad porque ninguna trampa usa el objetivo. - los
deleteProperty
trap es una trampa para borrar propiedades propias. El proxy representa la herencia, por lo que esto no tendría mucho sentido. Dejé que intentara eliminar el objetivo, que de todos modos no debería tener ninguna propiedad. - los
isExtensible
trap 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 dejé que redirigiera la operación al objetivo, que será extensible. - los
apply
yconstruct
Las trampas son trampas para llamar 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"
Actualización (2019): La publicación original se está volviendo bastante desactualizada. Este artículo (ahora enlace de archivo de Internet, ya que el dominio desapareció) y su biblioteca GitHub asociada son un buen enfoque moderno.
Publicación original:
Herencia múltiple [edit, not proper inheritance of type, but of properties; mixins] en Javascript es bastante sencillo si usa prototipos construidos en lugar de objetos genéricos. Aquí hay dos clases principales para heredar:
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 “nombre” en cada caso, lo que podría ser un problema si los padres no se pusieran de acuerdo sobre cómo se debe manejar “nombre”. Pero son compatibles (redundantes, en realidad) en este caso.
Ahora solo necesitamos una clase que herede de ambos. La herencia se realiza mediante llamaing la función constructora (sin usar la nueva palabra clave) para los prototipos y los constructores de objetos. Primero, el prototipo debe heredar de los prototipos principales
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 puede 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();
Este usa Object.create
para hacer un prototipo de cadena real:
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
así que eso obj.a === 1
, obj.b === 3
etc.
Si crees que ha resultado de ayuda nuestro artículo, nos gustaría que lo compartas con el resto seniors de esta manera nos ayudas a dar difusión a este contenido.