Avviso: questo è un post lungo.
Manteniamolo semplice. Voglio evitare di dover prefisso il nuovo operatore ogni volta che chiamo un costruttore in JavaScript. Questo perché tendo a dimenticarlo, e il mio codice si rovina male.
Il modo semplice per aggirare questo è ...
function Make(x) {
if ( !(this instanceof arguments.callee) )
return new arguments.callee(x);
// do your stuff...
}
Ma ho bisogno che accetti la variabile no. di argomenti, come questo ...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
La prima soluzione immediata sembra essere il metodo "applica" come questo ...
function Make() {
if ( !(this instanceof arguments.callee) )
return new arguments.callee.apply(null, arguments);
// do your stuff
}
Questo è WRONG comunque - il nuovo oggetto è passato al metodo apply
e NON al nostro costruttore arguments.callee
.
Ora ho trovato tre soluzioni. La mia semplice domanda è: quale sembra migliore. Oppure, se hai un metodo migliore, dillo.
Prima - usa eval()
per creare dinamicamente il codice JavaScript che chiama il costruttore.
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...
}
Secondo - Ogni oggetto ha una proprietà __proto__
che è un collegamento "segreto" al suo oggetto prototipo. Fortunatamente questa proprietà è scrivibile.
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;
}
Terzo - Questo è qualcosa di simile alla seconda soluzione.
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;
-
La soluzione
-
eval
sembra maldestra e viene fornita con tutti i problemi di "valutazione del male". -
__proto__
solution non è standard e il "Great Browser of mIsERY" non lo onora. -
La terza soluzione sembra eccessivamente complicata.
Ma con tutte le tre soluzioni precedenti, possiamo fare qualcosa di simile, che non possiamo altrimenti ...
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();
Così efficacemente le soluzioni di cui sopra ci danno costruttori "veri" che accettano la variabile no. di argomenti e non richiedono new
. Qual è la tua opinione su questo.
- UPDATE -
Alcuni hanno detto "basta lanciare un errore". La mia risposta è: stiamo facendo un'app pesante con 10+ costruttori e penso che sarebbe molto più maneggevole se ogni costruttore potesse "abilmente" gestire quell'errore senza lanciare messaggi di errore sulla console.