Aviso: Este é um post longo.
Vamos simplificar. Quero evitar ter que prefixar o novo operador toda vez que chamo um construtor em JavaScript. Isso ocorre porque costumo esquecê-lo e meu código estraga tudo.
A maneira simples de contornar isso é isso ...
function Make(x) {
if ( !(this instanceof arguments.callee) )
return new arguments.callee(x);
// do your stuff...
}
Mas, eu preciso disso para aceitar a variável no. de argumentos, assim ...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
A primeira solução imediata parece ser o método 'apply' como este ...
function Make() {
if ( !(this instanceof arguments.callee) )
return new arguments.callee.apply(null, arguments);
// do your stuff
}
No entanto, isso está errado - o novo objeto é passado para o apply
método e NÃO para o nosso construtor arguments.callee
.
Agora, eu vim com três soluções. Minha pergunta simples é: qual parece melhor. Ou, se você tiver um método melhor, informe.
Primeiro - use eval()
para criar dinamicamente o código JavaScript que chama o construtor.
function Make(/* ... */) {
if ( !(this instanceof arguments.callee) ) {
// collect all the arguments
var arr = [];
for ( var i = 0; arguments[i]; i++ )
arr.push( 'arguments[' + i + ']' );
// create code
var code = 'new arguments.callee(' + arr.join(',') + ');';
// call it
return eval( code );
}
// do your stuff with variable arguments...
}
Segundo - Todo objeto tem __proto__
propriedade que é um link 'secreto' para o seu objeto protótipo. Felizmente, essa propriedade é gravável.
function Make(/* ... */) {
var obj = {};
// do your stuff on 'obj' just like you'd do on 'this'
// use the variable arguments here
// now do the __proto__ magic
// by 'mutating' obj to make it a different object
obj.__proto__ = arguments.callee.prototype;
// must return obj
return obj;
}
Terceiro - isso é algo semelhante à segunda solução.
function Make(/* ... */) {
// we'll set '_construct' outside
var obj = new arguments.callee._construct();
// now do your stuff on 'obj' just like you'd do on 'this'
// use the variable arguments here
// you have to return obj
return obj;
}
// now first set the _construct property to an empty function
Make._construct = function() {};
// and then mutate the prototype of _construct
Make._construct.prototype = Make.prototype;
eval
solução parece desajeitada e vem com todos os problemas de "avaliação ruim".__proto__
solução não é padrão e o "Grande Navegador da Misericórdia" não a honra.A terceira solução parece excessivamente complicada.
Mas com todas as três soluções acima, podemos fazer algo assim, que não podemos de outra forma ...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
m1 instanceof Make; // true
m2 instanceof Make; // true
m3 instanceof Make; // true
Make.prototype.fire = function() {
// ...
};
m1.fire();
m2.fire();
m3.fire();
Tão efetivamente as soluções acima nos dão construtores "verdadeiros" que aceitam a variável no. de argumentos e não exige new
. Qual é a sua opinião sobre isso.
- ATUALIZAÇÃO -
Alguns disseram "apenas jogue um erro". Minha resposta é: estamos fazendo um aplicativo pesado com mais de 10 construtores e acho que seria muito mais difícil se todos os construtores pudessem "inteligentemente" lidar com esse erro sem lançar mensagens de erro no console.
fonte
Make()
semnew
devido Make é capitalizado e, portanto, assume que é um construtornew
? Porque se for o último, você provavelmente está perguntando no site errado. Se for o primeiro, convém não ignorar sugestões sobre o uso de novos e detectar erros tão rapidamente ... Se seu aplicativo é realmente "pesado", a última coisa que você quer é um mecanismo de construção exagerado para abrandá-lo.new
, por todo o flack que obtém, é bem rápido.Respostas:
Em primeiro lugar,
arguments.callee
é obsoleto no ES5 estrito, portanto não o usamos. A solução real é bastante simples.Você não usa
new
nada.Isso é um pé no saco certo?
Experimentar
enhance
Agora é claro que isso requer o ES5, mas todo mundo usa o calço ES5, certo?
Você também pode estar interessado em padrões alternativos de js OO
Como um aparte, você pode substituir a opção dois por
Caso você queira seu próprio
Object.create
calço ES5 , é realmente fácilfonte
Object.create
. E o pré ES5? ES5-Shim listaObject.create
como DUBIOUS.__proto__
coisa lá, então ainda estamos no mesmo ponto. Como o pré ES5 não existe uma maneira mais fácil de alterar o protótipo. De qualquer forma, sua solução parece mais elegante e prospectiva. +1 para isso. (meu limite de votação é atingido)Object.create
calço é praticamente minha terceira solução, mas menos complicada e com melhor aparência do que a minha, é claro.A resposta óbvia seria não esquecer a
new
palavra - chave.Você está mudando a estrutura e o significado da linguagem.
O que, na minha opinião, e pelo bem dos futuros mantenedores do seu código, é uma ideia horrível.
fonte
new
ou não, consideraria isso mais sustentável.;
instruções to end. (Inserção automática de ponto e vírgula)new
ou não é semanticamente idêntico . Não são nenhum caso sutis onde este invariante é quebrado. É por isso que é bom e por que você deseja usá-lo.A solução mais fácil é apenas lembrar
new
e lançar um erro para tornar óbvio que você esqueceu.Faça o que fizer, não use
eval
. Eu evitaria usar propriedades não padrão, como__proto__
especificamente porque elas não são padrão e sua funcionalidade pode mudar.fonte
.__proto__
é o diaboNa verdade, eu escrevi um post sobre isso. http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html
E você pode até generalizá-lo para não precisar adicioná-lo ao topo de todo construtor. Você pode ver isso visitando meu post
Isenção de responsabilidade Eu não uso isso no meu código, apenas o publiquei pelo valor didático. Descobri que esquecer um
new
é um bug fácil de detectar. Como outros, acho que não precisamos disso para a maioria dos códigos. A menos que você esteja escrevendo uma biblioteca para criar herança JS, nesse caso, você poderia usar de um único local e já estaria usando uma abordagem diferente da herança direta.fonte
var x = new Ctor();
e depois tiver x comothis
evar y = Ctor();
, isso não se comportaria como esperado.this
", você pode postar um jsfiddle para mostrar o problema em potencial?Ctor.call(ctorInstance, 'value')
. Não vejo um cenário válido para o que você está fazendo. Para construir um objeto, usevar x = new Ctor(val)
ouvar y=Ctor(val)
. Mesmo que houvesse um cenário válido, minha afirmação é de que você pode ter construtores sem usarnew Ctor
, não que possa ter construtores que trabalhem usandoCtor.call
Veja jsfiddle.net/JHNcR/2Que tal agora?
EDIT: esqueci de adicionar:
"Se seu aplicativo é realmente 'pesado', a última coisa que você deseja é um mecanismo de construção exagerado para abrandá-lo"
Eu concordo absolutamente - ao criar 'coisa' acima sem a palavra-chave 'nova', ela é mais lenta / pesada do que com ela. Os erros são seus amigos, porque eles dizem o que há de errado. Além disso, eles dizem aos seus colegas desenvolvedores o que estão fazendo de errado.
fonte