Saltar al contenido

Herencia múltiple / prototipos en JavaScript

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 el in operador. yo suelo some para comprobar si al menos un prototipo contiene la propiedad.
  • los get trap es una trampa para obtener valores de propiedad. yo suelo find para encontrar el primer prototipo que contiene esa propiedad, y devuelvo el valor, o llamo al captador en el receptor apropiado. Esto es manejado por Reflect.get. Si ningún prototipo contiene la propiedad, regreso. undefined.
  • los set trap es una trampa para establecer valores de propiedad. yo suelo find 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 por Reflect.set.
  • los enumerate trampa es una trampa para for...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 para Object.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 para Object.getOwnPropertyDescriptor(). Hacer que todas las propiedades enumerables aparezcan como propiedades propias en el ownKeys la trampa no es suficiente, for...in los bucles obtendrán el descriptor para comprobar si son enumerables. Entonces uso find 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 y defineProperty 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 implica instanceof 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 y construct 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 === 3etc.

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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *