ES6 permite estender objetos especiais. Portanto, é possível herdar da função. Esse objeto pode ser chamado como uma função, mas como posso implementar a lógica para essa chamada?
class Smth extends Function {
constructor (x) {
// What should be done here
super();
}
}
(new Smth(256))() // to get 256 at this call?
Qualquer método de classe obtém referência à instância de classe via this
. Mas quando é chamado como uma função, this
refere-se a window
. Como posso obter a referência à instância da classe quando ela é chamada como uma função?
super(x)
(ou seja, passá-lo paraFunction
)? Não tenho certeza seFunction
realmente pode ser estendido.Error
, entre outros.Function
é simplesmente um construtor de função. A implementação da função deve ser passada para o construtor. Se você não quiserSmth
aceitar uma implementação, você tem que fornecê-la no construtor, ou sejasuper('function implementation here')
.Function
construtor (tempo de execução), que é muito diferente de uma expressão de função (sintaxe).Respostas:
A
super
chamada invocará oFunction
construtor, que espera uma string de código. Se quiser acessar seus dados de instância, você pode apenas codificá-los:mas isso não é muito satisfatório. Queremos usar um fechamento.
Ter a função retornada como um fechamento que pode acessar suas variáveis de instância é possível, mas não é fácil. A boa notícia é que você não precisa chamar
super
se não quiser - você ainda podereturn
arbitrar objetos de seus construtores de classe ES6. Neste caso, faríamosMas podemos fazer ainda melhor e abstrair essa coisa de
Smth
:É certo que isso cria um nível adicional de indireção na cadeia de herança, mas isso não é necessariamente uma coisa ruim (você pode estendê-lo em vez do nativo
Function
). Se você quiser evitá-lo, usemas observe que
Smth
não herdaráFunction
propriedades estáticas dinamicamente .fonte
Smth
instâncias sejaminstanceof Smth
(como todos esperariam). Você pode omitir aObject.setPrototypeOf
chamada se não precisar deste ou de qualquer um dos métodos de protótipo declarados em sua classe.Object.setPrototypeOf
não é muito perigoso para a otimização, desde que seja feito logo após a criação do objeto. Apenas se você alterar o [[protótipo]] de um objeto para frente e para trás durante sua vida útil, ele ficará ruim.this
ereturn
um objeto.Esta é uma abordagem para criar objetos que podem ser chamados que referenciam corretamente seus membros de objeto e mantêm a herança correta, sem mexer com protótipos.
Simplesmente:
Estenda esta classe e adicione um
__call__
método, mais abaixo ...Uma explicação no código e comentários:
Ver em repl.it
Explicações adicionais sobre
bind
:function.bind()
funciona muito parecidofunction.call()
e eles compartilham uma assinatura de método semelhante:fn.call(this, arg1, arg2, arg3, ...);
mais em mdnfn.bind(this, arg1, arg2, arg3, ...);
mais em mdnEm ambos, o primeiro argumento redefine o
this
contexto dentro da função. Argumentos adicionais também podem ser associados a um valor. Mas ondecall
imediatamente chama a função com os valores vinculados,bind
retorna um objeto de função "exótico" que envolve o original de forma transparente, comthis
e quaisquer argumentos predefinidos.Então, quando você define uma função,
bind
alguns de seus argumentos:Você chama a função vinculada apenas com os argumentos restantes, seu contexto é predefinido, neste caso, para
['hello']
.fonte
bind
funciona (ou seja, por que ele retorna uma instância deExFunc
)?bind
retorna um objeto de função transparente que envolve o objeto de função no qual foi chamado, que é nosso objeto chamável, apenas com othis
rebote do contexto. Portanto, ele realmente retorna uma instância encapsulada de forma transparente deExFunc
. Postagem atualizada com mais informações sobrebind
.constructor
depoisbind
emExFunc
. Em subclasses de ExFunc, todos os membros são acessíveis. Quanto ainstanceof
; em es6, as funções associadas são chamadas de exóticas, portanto, seus funcionamentos internos não são aparentes, mas estou pensando que ela passa a chamada para seu destino empacotado, viaSymbol.hasInstance
. É muito parecido com um Proxy, mas é um método simples para obter o efeito desejado. Sua assinatura é semelhante, mas não a mesma.__call__
não consigo acessarthis.a
outhis.ab()
. por exemplo, repl.it/repls/FelineFinishedDesktopenvironmentVocê pode envolver a instância Smth em um Proxy com uma
apply
(e talvezconstruct
) armadilha:fonte
this
ainda é uma função vazia (verificarnew Smth().toString()
).setPrototypeOf
e não diz nada sobre proxies. Mas acho que os proxies podem ser tão problemáticos quantosetPrototypeOf
. E sobretoString
, ele pode ser sombreado com um método personalizado emSmth.prototype
. O nativo é dependente da implementação de qualquer maneira.construct
armadilha para especificar o comportamento denew new Smth(256)()
. E adicione métodos personalizados que sombreiam os nativos que acessam o código de uma função, comotoString
Bergi observou.apply
método implementado da maneira que deve ser usado, ou é apenas uma demonstração e eu preciso procurar mais informaçõesProxy
eReflect
usá-lo da maneira adequada?Segui o conselho da resposta de Bergi e envolvi-o em um módulo NPM .
fonte
Atualizar:
Infelizmente, isso não funciona muito bem porque agora está retornando um objeto de função em vez de uma classe, então parece que isso realmente não pode ser feito sem modificar o protótipo. Lame.
Basicamente, o problema é que não há como definir o
this
valor doFunction
construtor. A única maneira de realmente fazer isso seria usar o.bind
método posteriormente; no entanto, ele não é muito amigável.Poderíamos fazer isso em uma classe base auxiliar, no entanto
this
, não se torna disponível até depois dasuper
chamada inicial , por isso é um pouco complicado.Exemplo de trabalho:
(O exemplo requer um navegador moderno ou
node --harmony
.)Basicamente, a função base
ClassFunction
extends envolverá aFunction
chamada do construtor com uma função customizada que é semelhante.bind
, mas permite a vinculação mais tarde, na primeira chamada. Em seguida, noClassFunction
próprio construtor, ele chama a função retornada dasuper
qual agora é a função vinculada, passandothis
para concluir a configuração da função de vinculação personalizada.Isso tudo é um pouco complicado, mas evita a mutação do protótipo, que é considerado formato incorreto por motivos de otimização e pode gerar avisos nos consoles do navegador.
fonte
bound
irá se referir à função que vocêreturn
daquela classe anônima. Basta nomeá-lo e consultá-lo diretamente. Eu também recomendaria evitar passar strings de código, elas são apenas uma bagunça para trabalhar (em cada etapa do processo de desenvolvimento).extends
realmente não parece funcionar como esperado, poisFunction.isPrototypeOf(Smth)
e tambémnew Smth instanceof Function
são falsos.console.log((new Smth) instanceof Function);
étrue
para mim no Node v5.11.0 e no Firefox mais recente.new Smth instanceof Smth
que não está funcionando com sua solução. Além disso, nenhum método deSmth
estará disponível em suas instâncias - já que você apenas retorna um padrãoFunction
, não umSmth
.extend Function
também tornanew Smth instanceof Smth
falso.Em primeiro lugar, cheguei a uma solução com
arguments.callee
, mas foi horrível.Eu esperava que ele quebrasse no modo estrito global, mas parece que funciona mesmo aí.
Era um péssimo jeito por usar
arguments.callee
, passar o código como string e forçar sua execução em modo não estrito. Mas então a ideia de substituirapply
apareceu.E o teste, mostrando que sou capaz de executar essa função de diferentes maneiras:
Versão com
na verdade contém
bind
funcionalidade:Versão com
torna
call
eapply
emwindow
inconsistentes:portanto, o cheque deve ser movido para
apply
:fonte
this
é janela, não indefinida, portanto a função criada não está em modo estrito (pelo menos em cromo).Function
não está no modo estrito. Embora horrível, é +1 interessante. Você provavelmente não seria capaz de andar mais uma corrente embora.Esta é a solução que desenvolvi que atende a todas as minhas necessidades de extensão de funções e me serviu muito bem. Os benefícios desta técnica são:
ExtensibleFunction
, o código é idiomático de estender qualquer classe ES6 (não, mexer com construtores ou proxies fingidos).instanceof
/.constructor
retorna os valores esperados..bind()
.apply()
e.call()
todos funcionam conforme o esperado. Isso é feito substituindo esses métodos para alterar o contexto da função "interna" em oposição àExtensibleFunction
instância (ou de sua subclasse)..bind()
retorna uma nova instância do construtor de funções (seja eleExtensibleFunction
ou uma subclasse). Ele usaObject.assign()
para garantir que as propriedades armazenadas na função associada sejam consistentes com as da função de origem.Symbol
, que pode ser ofuscado por módulos ou um IIFE (ou qualquer outra técnica comum de privatizar referências).E sem mais delongas, o código:
Editar
Já que estava com vontade, decidi publicar um pacote para isso no npm.
fonte
Há uma solução simples que tira proveito dos recursos funcionais do JavaScript: Passe a "lógica" como um argumento-função para o construtor de sua classe, atribua os métodos dessa classe a essa função e, em seguida, retorne essa função do construtor como resultado :
O acima foi testado em Node.js 8.
Uma deficiência do exemplo acima é que ele não suporta métodos herdados da cadeia da superclasse. Para oferecer suporte a isso, basta substituir "Object. GetOwnPropertyNames (...)" por algo que retorna também os nomes dos métodos herdados. Acredito que como fazer isso está explicado em alguma outra pergunta-resposta no Stack Overflow :-). BTW. Seria bom se o ES7 adicionasse um método para produzir nomes de métodos herdados também ;-).
Se você precisar suportar métodos herdados, uma possibilidade é adicionar um método estático à classe acima que retorna todos os nomes de métodos herdados e locais. Em seguida, chame isso do construtor. Se você estender essa classe Funk, também obterá aquele método estático herdado junto.
fonte