Em javascript, como você pode converter uma “função construtora” em uma função regular que pode ser chamada sem a new
palavra – chave?
A implementação
/**
Take a constructor and return a function that can be called without new
but have the same effect as calling the constructor with new.
Useful for passing constructors to _.map, or passing arbitrary
arguments to the constructor using fn.apply(null, arguments)
*/
function ctor_fn(ctor) {
return function() {
var object = Object.create(ctor.prototype);
ctor.apply(object, arguments);
return object;
}
}
Motivação
Suponha que você tenha uma função construtora Button
que recebe dois argumentos: text
e callback
. Você pode criar um novo objeto de botão como este: new Button("Clickme", callback)
.
Se você precisar criar uma função que tenha o mesmo efeito de chamar Button, mas sem usar a new
palavra – chave, você pode fazer algo como:
function CreateButton(text, callback) {
return new Button(text, callback);
}
Fácil. Agora você pode passar CreateButton
como uma função que recebe argumentos e retorna um objeto. Você pode até chamá-lo desta forma:
CreateButton.apply(null, ["click me", callback]);
Mas agora, e se você quiser fazer o mesmo com outros 10 construtores? Como podemos generalizar essa abordagem?
As funções do Javascript têm e , mas não há nada que faça o equivalente a mas com o efeito de ter a palavra – chave na frente dele..call
.apply
.apply
new
Na verdade, a solução é simples: tudo o que você precisa fazer é chamar apply
e passar como primeiro argumento algum tipo de objeto “vazio”, por assim dizer. No entanto, para realmente emular a new
palavra – chave corretamente, esse objeto vazio deve ter o protótipo da função construtora; caso contrário, não é o mesmo que chamar a função com a new
palavra – chave.
Exemplo
Abra o console dev do seu navegador e tente o seguinte:
Primeiro, vamos criar alguma função construtora.
function Button(text, callback) {
var self = this;
self.text = text;
var noop = function() {};
self.callback = callback || noop;
self.as_element = function() {
var button = document.createElement("button");
button.innerText = self.text;
button.addEventListener("click", self.callback);
return button;
}
}
Nada de especial aÃ. Recebe um rótulo e uma função de retorno de chamada e fornece um método para criar um elemento de botão DOM com base nisso.
Agora, para testar se funciona, crie alguma instância e veja se ela se comporta conforme o esperado:
> b1 = new Button("test1", function() { console.log("Test 1 has been clicked"); })
<- Button {text: "test1", callback: function, as_element: function}
> b1.as_element()
<- <button>​test1​</button>​
> b1.as_element().click()
Test 1 has been clicked
<- undefined
Observe que estou fazendo isso no console de desenvolvimento do Chrome. As linhas que começam com <-
indicam o valor retornado da linha inserida acima dela.
Observe também como o console do Chrome exibe o objeto de botão (segunda linha): ele diz . Este é o Chrome informando qual função foi usada para construir este objeto.Button { .... }
OK, então funciona como esperado ao usar a nova palavra-chave. Agora vamos transformá-lo em uma função usando o nosso ctor_fn
acima:
CreateButton = ctor_fn(Button);
Veja se funciona:
> b2 = CreateButton("test2", function() { console.log("test2 clicked!! it works!!"); })
<- Button {text: "test2", callback: function, as_element: function}
> b2.as_element()
<- <button>​test2​</button>​
> b2.as_element().click()
test2 clicked!! it works!!
<- undefined
Funciona exatamente como se fosse chamado com new
. O Chrome até reconhece que foi construÃdo a partir dessa Button
função! Por quê? Porque costumávamos Object.create(ctor.prototype)
criar o objeto. Sim, não usamos Button
para criar o objeto, mas esse é o seu protótipo, então é a mesma coisa.
Se você solicitar o construtor do objeto, obterá o seguinte:
> b2.constructor.name
<- "Button"
Que tal apply
? Podemos chamar CreateButton.apply
para criar o objeto e esperar se comportar corretamente?
> b3 = CreateButton.apply(null, ["test3", function() { console.log("Test3 clicked! Apply also works!"); }])
<- Button {text: "test3", callback: function, as_element: function}
> b3.as_element()
<- <button>​test3​</button>​
> b3.as_element().click()
Test3 clicked! Apply also works!
<- undefined
Sim, funciona!
Então aà está.