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 deobjusar un proxy que redirigirá las operaciones fundamentales al objeto apropiado.Yo uso estas trampas:
hastrampa es una trampa para elinoperador . Yo usosomepara verificar si al menos un prototipo contiene la propiedad.gettrampa es una trampa para obtener valores de propiedad. Utilizofindpara 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.settrampa es una trampa para establecer valores de propiedad. Utilizofindpara 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.enumeratetrampa es una trampa parafor...inbucles . 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.
ownKeystrampa es una trampa paraObject.getOwnPropertyNames(). Desde ES7,for...inbucles 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.getOwnPropertyDescriptortrampa es una trampa paraObject.getOwnPropertyDescriptor(). Hacer que todas las propiedades enumerables aparezcan como propiedades propias en laownKeystrampa no es suficiente, losfor...inbucles obtendrán el descriptor para verificar si son enumerables. Por lo tanto, utilizofindpara 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.preventExtensionsydefinePropertysolo 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
getPrototypeOftrampa , pero no hay una forma adecuada de devolver los múltiples prototipos. Esto implicainstanceofque tampoco funcionará. Por lo tanto, dejo que obtenga el prototipo del objetivo, que inicialmente es nulo.setPrototypeOftrampa 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.deletePropertytrampa 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.isExtensibletrampa 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.applyyconstructson 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.createpara 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.
instanceOfAquí 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
Ben el futuro.Para heredar las propiedades de instancia
thisAythisBusamos 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:Bhereda el prototipo deAChereda el prototipo deBobjCes una instancia deCEsta 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.prototypevarias veces no produce múltiples prototipos. Por ejemplo, si lo hubiera hechoAnimal.isAlive = true,Cat.isAliveaú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
instanceofla 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
classpalabra 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
makeClassfunción acepta unoObjectde los nombres de clase padre mapeados a clases padre. También acepta una función que devuelveObjectpropiedades 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
makeClassfunción en sí, que hace bastante trabajo. Aquí está, junto con el resto del código. He comentadomakeClassbastante:La
makeClassfunció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 unaDragonclase 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
makeClasscerca notará un fenómeno indeseable bastante significativo que ocurre silenciosamente cuando se ejecuta el código anterior: ¡ crear una instanciaRunningFlyingdará como resultado DOS llamadas alNamedconstructor!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 clasesByC, que ambas heredan deA, y la claseBCque hereda deByC(y por lo tanto conceptualmente "doble hereda" deA):Si queremos evitar la
BCdoble invocación,A.prototype.inites 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
protosunaObjectinformació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 parapropertiesFnFunction:Todo el propósito del cambio anterior
makeClasses para que tengamos un argumento adicional suministradopropertiesFncuando 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 elSetque 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 cuandoBCse inicializa una instancia de . Pero hay tres desventajas, la tercera de las cuales es muy crítica :util.invokeNoDuplicatesfunció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 esedupsparámetro molesto , que realmente necesita ser definido en cada función de la clase . Ay.NiftyClassanula una funciónniftyFunctiony la usautil.invokeNoDuplicates(this, 'niftyFunction', ...)para ejecutarla sin invocación duplicada,NiftyClass.prototype.niftyFunctionllamará a la función nombradaniftyFunctionde 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 . SiNiftyClassheredaCoolClassyGoodClass, y ambas clases principales proporcionanniftyFunctiondefiniciones propias,NiftyClass.prototype.niftyFunctionnunca (sin arriesgarse a invocación múltiple) podrá:NiftyClassprimero, luego la lógica especializada de las clases padreNiftyClassen cualquier punto que no sea después de que se haya completado toda la lógica principal especializadaniftyFunctioncompleto 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óndeparentNameestá 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 casotestFnrecibiría el resultado de la lógica especializada para el padre nombradoparentNamey devolvería untrue/falsevalor que indica si el cortocircuito debería ocurrir)util.invokeNoDuplicatesBlackListedParents(blackList, instance, fnName, ...)(en este casoblackListsería unoArrayde 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
makeClassque 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
makeClassimplementació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
childhereda deparent1yparent2). 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