He llegado a un punto en el que necesito tener algún tipo de herencia múltiple rudimentaria en JavaScript. (No estoy aquí para discutir si es una buena idea o no, así que por favor guarde esos comentarios para usted).
Solo quiero saber si alguien ha intentado esto con algún (o no) éxito, 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 prototipo (es decir, cada prototipo podría tener su propia cadena adecuada), pero en un orden de precedencia dado (lo hará buscar las cadenas para la primera definición).
Para demostrar cómo esto es teóricamente posible, podría lograrse 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?
Respuestas:
Se puede lograr la herencia múltiple en ECMAScript 6 utilizando objetos Proxy .
Implementación
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 deobj
usar un proxy que redirigirá las operaciones fundamentales al objeto apropiado.Yo uso estas trampas:
has
trampa es una trampa para elin
operador . Yo usosome
para verificar si al menos un prototipo contiene la propiedad.get
trampa 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 captador en el receptor apropiado. Esto es manejado porReflect.get
. Si ningún prototipo contiene la propiedad, regresoundefined
.set
trampa 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 setter o ningún prototipo contiene la propiedad, el valor se define en el receptor apropiado. Esto es manejado porReflect.set
.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 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.
ownKeys
trampa es una trampa paraObject.getOwnPropertyNames()
. Desde ES7,for...in
bucles siguen llamando [[GetPrototypeOf]] y obteniendo las propias propiedades de cada uno. Entonces, para hacer que itere las propiedades de todos los prototipos, uso esta trampa para hacer que todas las propiedades heredables enumerables aparezcan como propiedades propias.getOwnPropertyDescriptor
trampa es una trampa paraObject.getOwnPropertyDescriptor()
. Hacer que todas las propiedades enumerables aparezcan como propiedades propias en laownKeys
trampa no es suficiente, losfor...in
bucles obtendrán el descriptor para verificar si son enumerables. Por lo tanto, utilizofind
para encontrar el primer prototipo que contiene esa propiedad, e itero su cadena prototípica hasta que encuentro al dueño de la propiedad, y devuelvo su descriptor. Si ningún prototipo contiene la propiedad, regresoundefined
. El descriptor se modifica para hacerlo configurable, de lo contrario podríamos romper algunos invariantes proxy.preventExtensions
ydefineProperty
solo se incluyen para evitar que estas operaciones modifiquen el objetivo proxy. De lo contrario, podríamos terminar rompiendo algunos invariantes proxy.Hay más trampas disponibles, que no uso
getPrototypeOf
trampa , pero no hay una forma adecuada de devolver los múltiples prototipos. Esto implicainstanceof
que tampoco funcionará. Por lo tanto, dejo que obtenga el prototipo del objetivo, que inicialmente es nulo.setPrototypeOf
trampa podría agregarse y aceptar una serie 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 que no es muy útil porque ninguna trampa usa el objetivo.deleteProperty
trampa es una trampa para eliminar propiedades propias. El proxy representa la herencia, por lo que esto no tendría mucho sentido. Lo dejé intentar la eliminación en el destino, que de todos modos no debería tener ninguna propiedad.isExtensible
trampa 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 dejé que redirija la operación al objetivo, que será extensible.apply
yconstruct
son trampas para llamar o crear instancias. Solo son útiles cuando el objetivo es una función o un constructor.Ejemplo
fuente
multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3})
Object.assign
) u obteniendo un gráfico bastante diferente, al final todos ellos están obteniendo una cadena de prototipo única entre los objetos. La solución proxy ofrece una ramificación en tiempo de ejecución, ¡y esto es genial!Actualización (2019): la publicación original está 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 [editar, no una herencia adecuada de tipo, sino de propiedades; mixins] en Javascript es bastante sencillo si utiliza prototipos construidos en lugar de los de objetos genéricos. Aquí hay dos clases principales para heredar:
Tenga en cuenta que he usado el mismo miembro "nombre" en cada caso, lo que podría ser un problema si los padres no estuvieran 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 llamando a la función constructora (sin usar la nueva palabra clave) para los prototipos y los constructores de objetos. Primero, el prototipo tiene que heredar de los prototipos principales.
Y el constructor tiene que heredar de los constructores principales:
Ahora puede cultivar, comer y cosechar diferentes instancias:
fuente
Array.call(...)
pero no parece afectar lo que sea que pasothis
.Array.prototype.constructor.call()
Este se usa
Object.create
para hacer una cadena de prototipo real:Por ejemplo:
volverá:
de modo que
obj.a === 1
,obj.b === 3
, etc.fuente
Me gusta la implementación de John Resig de una estructura de clases: http://ejohn.org/blog/simple-javascript-inheritance/
Esto puede extenderse simplemente a algo como:
que le permitirá pasar múltiples objetos de los cuales heredar.
instanceOf
Aquí perderá capacidad, pero eso es un hecho si desea herencia múltiple.mi ejemplo bastante complicado 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 una clase como:
que preservará la cadena del prototipo original, pero también tendrá un montón de código inútil en ejecución.
fuente
No se confunda con las implementaciones de framework JavaScript de herencia múltiple.
Todo lo que necesita hacer es usar Object.create () para crear un nuevo objeto cada vez con el objeto prototipo y las propiedades especificadas, luego asegúrese de cambiar Object.prototype.constructor en cada paso del camino si planea crear instancias
B
en el futuro.Para heredar las propiedades de instancia
thisA
ythisB
usamos 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
: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:
OOP en JavaScript: lo que necesita saber
fuente
De ninguna manera soy un experto en JavaScript OOP, pero si te entiendo correctamente, quieres algo como (pseudocódigo):
En ese caso, probaría algo como:
fuente
c.prototype
varias veces no produce múltiples prototipos. Por ejemplo, si lo hubiera hechoAnimal.isAlive = true
,Cat.isAlive
aún estaría indefinido.Es posible implementar herencia múltiple en JavaScript, aunque muy pocas bibliotecas lo hacen.
Podría señalar Ring.js , el único ejemplo que conozco.
fuente
Trabajé mucho en esto hoy e intenté lograrlo yo mismo en ES6. La forma en que lo hice fue usando Browserify, Babel y luego lo probé con Wallaby y pareció funcionar. Mi objetivo es extender 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 puede ver que la propiedad 'incluye' 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 reempaquete como módulo después de dormir un poco!)
Repo de Github: https://github.com/danieldram/array-includes-polyfill
fuente
Creo que es ridículamente simple. El problema aquí es que la clase secundaria solo se referirá a
instanceof
la primera clase a la que llamehttps://jsfiddle.net/1033xzyt/19/
fuente
Verifique el siguiente código que muestra compatibilidad con herencia múltiple. Hecho mediante el uso de la herencia proteotípica
fuente
Tengo bastante la función de permitir que las clases se definan con herencia múltiple. Permite código como el siguiente. En general, notará una desviación completa de las técnicas de clasificación nativas en javascript (por ejemplo, nunca verá la
class
palabra clave):para producir resultados como este:
A continuación se muestran las definiciones de clase:
Podemos ver que cada definición de clase que usa la
makeClass
función acepta unoObject
de los nombres de clase padre mapeados a clases padre. También acepta una función que devuelveObject
propiedades que contienen para la clase que se está definiendo. Esta función tiene un parámetroprotos
, que contiene suficiente información para acceder a cualquier propiedad definida por cualquiera de las clases principales.La pieza final requerida es la
makeClass
función en sí, que hace bastante trabajo. Aquí está, junto con el resto del código. He comentadomakeClass
bastante:La
makeClass
función también admite propiedades de clase; estos se definen prefijando los nombres de las propiedades con el$
símbolo (tenga en cuenta que el nombre de la propiedad final que resulte tendrá el$
eliminado). Con esto en mente, podríamos escribir unaDragon
clase especializada que modele el "tipo" del Dragón, donde la lista de tipos de Dragón disponibles se almacena en la Clase en lugar de en las instancias:Los desafíos de la herencia múltiple
Cualquiera que haya seguido el código de
makeClass
cerca notará un fenómeno indeseable bastante significativo que ocurre silenciosamente cuando se ejecuta el código anterior: ¡ crear una instanciaRunningFlying
dará como resultado DOS llamadas alNamed
constructor!Esto se debe a que el gráfico de herencia se ve así:
Cuando hay múltiples rutas a la misma clase padre en un gráfico de herencia de la subclase, las instancias de la subclase invocarán ese constructor de la clase padre varias veces.
Combatir esto no es trivial. Veamos algunos ejemplos con nombres de clase simplificados. Consideraremos la clase
A
, la clase padre más abstracta, las clasesB
yC
, que ambas heredan deA
, y la claseBC
que hereda deB
yC
(y por lo tanto conceptualmente "doble hereda" deA
):Si queremos evitar la
BC
doble invocación,A.prototype.init
es posible que debamos abandonar el estilo de llamar directamente a los constructores heredados. Necesitaremos cierto nivel de indirección para verificar si se producen llamadas duplicadas y cortocircuitos antes de que sucedan.Podríamos considerar cambiar los parámetros suministrados a la función de propiedades: junto con
protos
unaObject
información sin procesar que describe las propiedades heredadas, también podríamos incluir una función de utilidad para llamar a un método de instancia de tal manera que también se invoquen métodos principales, pero se detectan llamadas duplicadas y prevenido. Echemos un vistazo a dónde establecemos los parámetros parapropertiesFn
Function
:Todo el propósito del cambio anterior
makeClass
es para que tengamos un argumento adicional suministradopropertiesFn
cuando invoquemosmakeClass
. También debemos tener en cuenta que cada función definida en cualquier clase ahora puede recibir un parámetro después de todos los demás, llamadodup
, que es elSet
que contiene todas las funciones que ya se han llamado como resultado de llamar al método heredado:Este nuevo estilo realmente garantiza
"Construct A"
que solo se registre una vez cuandoBC
se inicializa una instancia de . Pero hay tres desventajas, la tercera de las cuales es muy crítica :util.invokeNoDuplicates
función, y pensar en cómo este estilo evita la invocación múltiple no es intuitivo e induce dolor de cabeza. También tenemos esedups
parámetro molesto , que realmente necesita ser definido en cada función de la clase . Ay.NiftyClass
anula una funciónniftyFunction
y la usautil.invokeNoDuplicates(this, 'niftyFunction', ...)
para ejecutarla sin invocación duplicada,NiftyClass.prototype.niftyFunction
llamará a la función nombradaniftyFunction
de cada clase padre que la defina, ignorará cualquier valor de retorno de esas clases y finalmente realizará la lógica especializada deNiftyClass.prototype.niftyFunction
. Esta es la única estructura posible . SiNiftyClass
heredaCoolClass
yGoodClass
, y ambas clases principales proporcionanniftyFunction
definiciones propias,NiftyClass.prototype.niftyFunction
nunca (sin arriesgarse a invocación múltiple) podrá:NiftyClass
primero, luego la lógica especializada de las clases padreNiftyClass
en cualquier punto que no sea después de que se haya completado toda la lógica principal especializadaniftyFunction
completo a un padre en particular especializadoPor supuesto, podríamos resolver cada problema con letras arriba definiendo funciones especializadas en
util
:util.invokeNoDuplicatesSubClassLogicFirst(instance, fnName, ...)
util.invokeNoDuplicatesSubClassAfterParent(parentName, instance, fnName, ...)
(dóndeparentName
está el nombre del padre cuya lógica especializada será seguida inmediatamente por la lógica especializada de las clases secundarias)util.invokeNoDuplicatesCanShortCircuitOnParent(parentName, testFn, instance, fnName, ...)
(en este casotestFn
recibiría el resultado de la lógica especializada para el padre nombradoparentName
y devolvería untrue/false
valor que indica si el cortocircuito debería ocurrir)util.invokeNoDuplicatesBlackListedParents(blackList, instance, fnName, ...)
(en este casoblackList
sería unoArray
de los nombres principales cuya lógica especializada debería omitirse por completo)Estas soluciones están disponibles, ¡ pero esto es un caos total ! Para cada estructura única que puede tomar una llamada de función heredada, necesitaríamos un método especializado definido en
util
. Qué desastre absoluto.Con esto en mente, podemos comenzar a ver los desafíos de implementar una buena herencia múltiple. La implementación completa de lo
makeClass
que proporcioné en esta respuesta ni siquiera considera el problema de la invocación múltiple o muchos otros problemas que surgen con respecto a la herencia múltiple.Esta respuesta se está haciendo muy larga. Espero que la
makeClass
implementación que incluí siga siendo útil, incluso si no es perfecta. ¡También espero que cualquier persona interesada en este tema haya adquirido más contexto para tener en cuenta mientras leen más!fuente
Echa un vistazo al paquete IeUnit .
El concepto de asimilación implementado en IeUnit parece ofrecer lo que está buscando de una manera bastante dinámica.
fuente
Aquí hay un ejemplo de encadenamiento de prototipos usando funciones de constructor :
Este concepto utiliza la definición de Yehuda Katz de una "clase" para JavaScript:
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". Nosotros solo usamos
new
.El orden de precedencia debería tener sentido. Primero se ve en el objeto de instancia, luego es el prototipo, luego el siguiente prototipo, etc.
También podemos modificar los prototipos que afectarán a todos los objetos creados en la clase.
Originalmente escribí algo de esto con esta respuesta .
fuente
child
hereda deparent1
yparent2
). Su ejemplo solo habla de una cadena.Un recién llegado en la escena es SimpleDeclare . Sin embargo, cuando se trata de herencia múltiple, aún terminará con copias de los constructores originales. Eso es una necesidad en Javascript ...
Merc.
fuente
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 admite algunas otras características interesantes como interfaces e inyección de dependencias
fuente
¿Qué tal esto? Implementa herencia múltiple en JavaScript:
Y aquí está el código para la función de utilidad specialize_with ():
Este es el código real que se ejecuta. Puede copiarlo y pegarlo en un archivo html y probarlo usted mismo. Funciona
Ese es el esfuerzo para implementar MI en JavaScript. No hay mucho código, más un saber hacer.
Por favor, siéntase libre de mirar mi artículo completo sobre esto, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS
fuente
Solo solía asignar qué clases necesito en las propiedades de otros, y agrego un proxy para señalarlas automáticamente, me gusta:
fuente