Cheguei a um ponto em que preciso ter algum tipo de herança múltipla rudimentar acontecendo em JavaScript. (Não estou aqui para discutir se é uma boa ideia ou não, por favor, guarde esses comentários para si mesmo.)
Eu só quero saber se alguém tentou isso com algum (ou não) sucesso, e como eles fizeram isso.
Para resumir, o que realmente preciso é ser capaz de ter um objeto capaz de herdar uma propriedade de mais de uma cadeia de protótipos (ou seja, cada protótipo pode ter sua própria cadeia), mas em uma ordem de precedência (será procure as cadeias para obter a primeira definição).
Para demonstrar como isso é teoricamente possível, isso poderia ser alcançado anexando a cadeia secundária ao final da cadeia primária, mas isso afetaria todas as instâncias de qualquer um desses protótipos anteriores e não é isso que eu quero.
Pensamentos?
fonte
Respostas:
Herança múltipla pode ser alcançada no ECMAScript 6 usando objetos Proxy .
Implementação
Explicação
Um objeto proxy consiste em um objeto de destino e em alguns traps, que definem o comportamento customizado para operações fundamentais.
Ao criar um objeto que herda de outro, usamos
Object.create(obj)
. Mas, neste caso, queremos herança múltipla; portanto, em vez deobj
usar um proxy que redirecionará operações fundamentais para o objeto apropriado.Eu uso essas armadilhas:
has
armadilha é uma armadilha para oin
operador . Usosome
para verificar se pelo menos um protótipo contém a propriedadeget
armadilha é uma armadilha para obter valores de propriedade. Eu usofind
para encontrar o primeiro protótipo que contém essa propriedade e retorno o valor ou chamo o getter no destinatário apropriado. Isso é tratado porReflect.get
. Se nenhum protótipo contiver a propriedade, eu retornareiundefined
.set
armadilha é uma armadilha para definir valores de propriedade. Utilizofind
para encontrar o primeiro protótipo que contém essa propriedade e chamo seu setter no receptor apropriado. Se não houver nenhum setter ou nenhum protótipo contiver a propriedade, o valor será definido no receptor apropriado. Isso é tratado porReflect.set
.enumerate
armadilha é uma armadilha parafor...in
loops . Eu itero as propriedades enumeráveis do primeiro protótipo, depois do segundo e assim por diante. Depois que uma propriedade é iterada, eu a armazeno em uma tabela de hash para evitar iterá-la novamente.Aviso : essa interceptação foi removida no rascunho do ES7 e está obsoleta nos navegadores.
ownKeys
armadilha é uma armadilha paraObject.getOwnPropertyNames()
. Desde o ES7, osfor...in
loops continuam chamando [[GetPrototypeOf]] e obtendo as próprias propriedades de cada um. Portanto, para fazer iterar as propriedades de todos os protótipos, eu uso essa interceptação para fazer com que todas as propriedades herdadas enumeráveis apareçam como propriedades próprias.getOwnPropertyDescriptor
armadilha é uma armadilha paraObject.getOwnPropertyDescriptor()
. Fazer com que todas as propriedades enumeráveis apareçam como propriedades próprias naownKeys
armadilha não é suficiente, osfor...in
loops receberão o descritor para verificar se são enumeráveis. Então, eu usofind
para encontrar o primeiro protótipo que contém essa propriedade e iteramos sua cadeia prototípica até encontrar o proprietário da propriedade e retornar seu descritor. Se nenhum protótipo contiver a propriedade, eu retornareiundefined
. O descritor é modificado para torná-lo configurável, caso contrário, poderíamos quebrar alguns invariantes de proxy.preventExtensions
edefineProperty
são incluídos apenas para impedir que essas operações modifiquem o destino do proxy. Caso contrário, poderíamos acabar quebrando alguns invariantes de proxy.Existem mais armadilhas disponíveis, que eu não uso
getPrototypeOf
armadilha pode ser adicionada, mas não há maneira adequada de retornar os vários protótipos. Isso implicainstanceof
que não funcionará também. Portanto, deixo que ele obtenha o protótipo do alvo, que inicialmente é nulo.setPrototypeOf
armadilha pode ser adicionada e aceitar uma matriz de objetos, que substituiria os protótipos. Isso é deixado como um exercício para o leitor. Aqui, deixei que ele modificasse o protótipo do alvo, o que não é muito útil porque nenhuma armadilha usa o alvo.deleteProperty
armadilha é uma armadilha para excluir propriedades próprias. O proxy representa a herança, portanto, isso não faria muito sentido. Eu deixei tentar a exclusão no destino, que não deveria ter nenhuma propriedade.isExtensible
armadilha é uma armadilha para obter a extensibilidade. Não é muito útil, uma vez que uma invariante o força a retornar a mesma extensibilidade que o alvo. Então, eu apenas deixei redirecionar a operação para o destino, que será extensível.apply
econstruct
são armadilhas para chamar ou instanciar. Eles são úteis apenas quando o destino é uma função ou um construtor.Exemplo
fonte
multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3})
Object.assign
) ou obtendo um gráfico bem diferente; no final, todas elas estão recebendo uma cadeia de protótipos únicos entre objetos. A solução de proxy oferece uma ramificação em tempo de execução, e isso é demais!Atualização (2019): A postagem original está ficando bastante desatualizada. Este artigo (agora link do arquivo da Internet, desde que o domínio foi embora) e sua biblioteca GitHub associada são uma boa abordagem moderna.
Post original: herança múltipla [editar, herança não apropriada do tipo, mas de propriedades; mixins] em Javascript é bastante simples se você usar protótipos construídos em vez de genéricos. Aqui estão duas classes pai para herdar:
Observe que eu usei o mesmo membro "nome" em cada caso, o que pode ser um problema se os pais não concordarem sobre como o "nome" deve ser tratado. Mas eles são compatíveis (redundantes, realmente) neste caso.
Agora só precisamos de uma classe que herda de ambos. A herança é feita chamando a função construtora (sem usar a nova palavra-chave) para os protótipos e construtores de objetos. Primeiro, o protótipo deve herdar dos protótipos pai
E o construtor deve herdar dos construtores pai:
Agora você pode crescer, comer e colher diferentes instâncias:
fonte
Array.call(...)
mas isso não parece afetar o que eu passothis
.Array.prototype.constructor.call()
Este usa
Object.create
para criar uma cadeia de protótipos real:Por exemplo:
retornará:
de modo que
obj.a === 1
,obj.b === 3
etc.fonte
Gosto da implementação de John Resig de uma estrutura de classes: http://ejohn.org/blog/simple-javascript-inheritance/
Isso pode ser simplesmente estendido para algo como:
o que permitirá que você passe vários objetos dos quais herdar. Você perderá a
instanceOf
capacidade aqui, mas isso é um fato, se você quiser herança múltipla.meu exemplo bastante complicado está disponível em https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js
Observe que há algum código morto nesse arquivo, mas permite herança múltipla se você quiser dar uma olhada.
Se você deseja herança encadeada (NÃO herança múltipla, mas para a maioria das pessoas é a mesma coisa), isso pode ser conseguido com Classe como:
que preservará a cadeia de protótipos original, mas você também terá muito código inútil em execução.
fonte
Não se confunda com implementações de estrutura JavaScript de herança múltipla.
Tudo o que você precisa fazer é usar Object.create () para criar um novo objeto toda vez com o objeto e propriedades de protótipo especificado e, em seguida, certifique-se de alterar o Object.prototype.constructor a cada passo do caminho, se você planeja instanciar
B
o futuro.Para herdar propriedades da instância
thisA
ethisB
usamos Function.prototype.call () no final de cada função de objeto. Isso é opcional se você só se preocupa em herdar o protótipo.Execute o seguinte código em algum lugar e observe
objC
:B
herda o protótipo deA
C
herda o protótipo deB
objC
é uma instância deC
Esta é uma boa explicação das etapas acima:
OOP em JavaScript: o que você precisa saber
fonte
Eu não sou de forma alguma um especialista em javascript OOP, mas se eu entendi corretamente, você deseja algo como (pseudo-código):
Nesse caso, eu tentaria algo como:
fonte
c.prototype
várias vezes não gera vários protótipos. Por exemplo, se você tivesseAnimal.isAlive = true
,Cat.isAlive
ainda seria indefinido.É possível implementar herança múltipla em JavaScript, embora poucas bibliotecas o façam.
Eu poderia apontar Ring.js , o único exemplo que conheço.
fonte
Hoje eu estava trabalhando muito nisso e tentando conseguir isso sozinho no ES6. O jeito que eu fiz isso foi usando o Browserify, Babel e depois testei com o Wallaby e ele pareceu funcionar. Meu objetivo é estender a matriz atual, incluir o ES6, ES7 e adicionar alguns recursos personalizados adicionais necessários no protótipo para lidar com dados de áudio.
O Wallaby passa em 4 dos meus testes. O arquivo example.js pode ser colado no console e você pode ver que a propriedade 'includes' está no protótipo da classe. Ainda quero testar isso amanhã.
Aqui está o meu método: (provavelmente refatorarei e reembalarei como um módulo depois de dormir!)
Repositório do Github: https://github.com/danieldram/array-includes-polyfill
fonte
Eu acho isso ridiculamente simples. O problema aqui é que a classe filho se refere apenas à
instanceof
primeira classe que você chamahttps://jsfiddle.net/1033xzyt/19/
fonte
Verifique o código abaixo do qual IS está mostrando suporte para herança múltipla. Feito usando a herança prototípica
fonte
Eu tenho bastante a função de permitir que classes sejam definidas com herança múltipla. Ele permite códigos como os seguintes. No geral, você observará uma saída completa das técnicas nativas de classificação em javascript (por exemplo, você nunca verá a
class
palavra - chave):para produzir uma saída como esta:
Aqui estão as definições das classes:
Podemos ver que cada definição de classe usando a
makeClass
função aceita umObject
nome de classe pai mapeado para classe pai. Ele também aceita uma função que retornaObject
propriedades contendo a classe que está sendo definida. Esta função possui um parâmetroprotos
, que contém informações suficientes para acessar qualquer propriedade definida por qualquer uma das classes-pai.A peça final necessária é a
makeClass
própria função, que faz bastante trabalho. Aqui está, junto com o restante do código. Eu comenteimakeClass
bastante:A
makeClass
função também suporta propriedades de classe; elas são definidas prefixando os nomes das propriedades com o$
símbolo (observe que o nome da propriedade final resultante será$
removido). Com isso em mente, poderíamos escrever umaDragon
classe especializada que modela o "tipo" do dragão, onde a lista de tipos de dragão disponíveis é armazenada na própria classe, e não nas instâncias:Os desafios da herança múltipla
Quem seguiu o código de
makeClass
perto notará um fenômeno indesejável bastante significativo ocorrendo silenciosamente quando o código acima for executado: instanciar aRunningFlying
resultará em DUAS chamadas aoNamed
construtor!Isso ocorre porque o gráfico de herança se parece com isso:
Quando existem vários caminhos para a mesma classe pai no gráfico de herança de uma subclasse da subclasse, as instanciações da subclasse invocam o construtor dessa classe-pai várias vezes.
Combater isso não é trivial. Vejamos alguns exemplos com nomes de classe simplificados. Consideraremos classe
A
, a classe pai mais abstrata, classesB
eC
, que herdam deA
, e classeBC
que herda deB
eC
(e, portanto, conceitualmente "herda duas vezes" deA
):Se quisermos impedir
BC
a chamada duplaA.prototype.init
, talvez seja necessário abandonar o estilo de chamar diretamente os construtores herdados. Precisamos de algum nível de indireção para verificar se chamadas duplicadas estão ocorrendo e entrar em curto-circuito antes que elas aconteçam.Poderíamos considerar mudar os parâmetros fornecidos para as propriedades funcionar: ao lado
protos
, umaObject
contendo dados brutos que descrevem propriedades herdadas, que poderia também incluir uma função de utilidade para chamar um método de exemplo, de tal forma que os métodos mãe também são chamados, mas chamadas duplicados são detectados e impedido. Vamos dar uma olhada em onde estabelecemos os parâmetros parapropertiesFn
Function
:Todo o objetivo da alteração acima
makeClass
é para que tenhamos um argumento adicional fornecidopropertiesFn
quando invocamosmakeClass
. Também devemos estar cientes de que todas as funções definidas em qualquer classe agora podem receber um parâmetro depois de todas as outras, nomeadasdup
,Set
que contém todas as funções que já foram chamadas como resultado da chamada do método herdado:Esse novo estilo realmente consegue garantir que
"Construct A"
seja registrado apenas uma vez quando uma instância deBC
for inicializada. Mas há três desvantagens, a terceira das quais é muito crítica :util.invokeNoDuplicates
função, e pensar em como esse estilo evita a invocação múltipla não é intuitivo e causa dor de cabeça. Também temos essedups
parâmetro irritante , que realmente precisa ser definido em todas as funções da classe . Ai.NiftyClass
substituir uma funçãoniftyFunction
e utilizáutil.invokeNoDuplicates(this, 'niftyFunction', ...)
-la para executá-la sem invocação duplicada,NiftyClass.prototype.niftyFunction
chamará a função nomeadaniftyFunction
de cada classe pai que a define, ignorará quaisquer valores de retorno dessas classes e, finalmente, executará a lógica especializada deNiftyClass.prototype.niftyFunction
. Essa é a única estrutura possível . SeNiftyClass
herdaCoolClass
eGoodClass
, e ambas as classes-pai fornecemniftyFunction
definições próprias,NiftyClass.prototype.niftyFunction
nunca (sem arriscar invocação múltipla) será capaz de:NiftyClass
primeiro a lógica especializada , depois a lógica especializada das classes paiNiftyClass
em qualquer ponto que não seja após a conclusão de toda lógica lógica especializadaniftyFunction
totalmenteObviamente, poderíamos resolver cada problema com letras acima, definindo funções especializadas em
util
:util.invokeNoDuplicatesSubClassLogicFirst(instance, fnName, ...)
util.invokeNoDuplicatesSubClassAfterParent(parentName, instance, fnName, ...)
(OndeparentName
é o nome do pai cuja lógica especializada será imediatamente seguida pela lógica especializada das classes filho)util.invokeNoDuplicatesCanShortCircuitOnParent(parentName, testFn, instance, fnName, ...)
(nesse casotestFn
, receberia o resultado da lógica especializada para o pai nomeadoparentName
e retornaria umtrue/false
valor indicando se o curto-circuito deveria ocorrer)util.invokeNoDuplicatesBlackListedParents(blackList, instance, fnName, ...)
(Nesse caso,blackList
seria umArray
dos nomes dos pais cuja lógica especializada deve ser ignorada por completo)Essas soluções estão todas disponíveis, mas isso é total caos ! Para toda estrutura exclusiva que uma chamada de função herdada pode suportar, precisaríamos de um método especializado definido em
util
. Que desastre absoluto.Com isso em mente, podemos começar a ver os desafios da implementação de uma boa herança múltipla. A implementação completa de
makeClass
I fornecida nesta resposta nem sequer considera o problema de invocação múltipla ou muitos outros problemas que surgem com relação à herança múltipla.Esta resposta está ficando muito longa. Espero que a
makeClass
implementação incluída ainda seja útil, mesmo que não seja perfeita. Eu também espero que qualquer pessoa interessada neste tópico tenha ganhado mais contexto para ter em mente enquanto lê mais!fonte
Dê uma olhada no pacote IeUnit .
A assimilação de conceito implementada no IeUnit parece oferecer o que você está procurando de uma maneira bastante dinâmica.
fonte
Aqui está um exemplo de encadeamento de protótipo usando funções de construtor :
Este conceito usa a definição de Yehuda Katz de "classe" para JavaScript:
Diferentemente da abordagem Object.create , quando as classes são criadas dessa maneira e queremos criar instâncias de uma "classe", não precisamos saber do que cada "classe" está herdando. Nós apenas usamos
new
.A ordem de precedência deve fazer sentido. Primeiro, ele olha no objeto de instância, depois é protótipo, depois no próximo protótipo, etc.
Também podemos modificar os protótipos que afetarão todos os objetos criados na classe.
Eu originalmente escrevi um pouco disso com esta resposta .
fonte
child
herda deparent1
eparent2
). Seu exemplo fala apenas de uma cadeia.Um retardatário na cena é SimpleDeclare . No entanto, ao lidar com várias heranças, você ainda terá cópias dos construtores originais. Isso é uma necessidade em Javascript ...
Merc.
fonte
Eu usaria ds.oop . É semelhante ao prototype.js e outros. torna a herança múltipla muito fácil e minimalista. (apenas 2 ou 3 kb) Também suporta alguns outros recursos interessantes, como interfaces e injeção de dependência
fonte
Que tal isso, implementa herança múltipla em JavaScript:
E aqui está o código para a função de utilitário specialize_with ():
Este é um código real que é executado. Você pode copiar e colar no arquivo html e tentar você mesmo. Isso funciona.
Esse é o esforço para implementar o MI em JavaScript. Não há muito código, mais conhecimento.
Sinta-se à vontade para consultar meu artigo completo sobre isso, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS
fonte
Eu apenas usei para atribuir quais classes eu preciso nas propriedades de outras pessoas e adicionar um proxy para apontar automaticamente para elas, como eu gosto:
fonte