Attenzione: questo è un post lungo.
Manteniamolo semplice. Voglio evitare di dover aggiungere il prefisso al 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 è questo ...
function Make(x) {
if ( !(this instanceof arguments.callee) )
return new arguments.callee(x);
// do your stuff...
}
Ma ho bisogno di questo per accettare 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
}
Tuttavia, questo è SBAGLIATO: il nuovo oggetto viene passato al apply
metodo e NON al nostro costruttore arguments.callee
.
Ora, ho escogitato tre soluzioni. La mia semplice domanda è: quale sembra la migliore. Oppure, se hai un metodo migliore, dillo.
Primo : utilizzare eval()
per creare dinamicamente 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 __proto__
proprietà 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 : è 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;
eval
la soluzione sembra goffa e presenta tutti i problemi di "malvagia valutazione".__proto__
la soluzione non è standard e il "Grande Browser di mIsERY" non lo onora.La terza soluzione sembra eccessivamente complicata.
Ma con tutte queste tre soluzioni, possiamo fare qualcosa del genere, che altrimenti non potremmo ...
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 n. di argomenti e non richiedono new
. Cosa ne pensi di questo.
-- AGGIORNARE --
Alcuni hanno detto "basta lanciare un errore". La mia risposta è: stiamo realizzando un'app pesante con oltre 10 costruttori e penso che sarebbe molto più utile se ogni costruttore potesse "intelligentemente" gestire l'errore senza inviare messaggi di errore sulla console.
Make()
senza new
perché Make è in maiuscolo e quindi presume che sia un costruttore
new
? Perché se è quest'ultimo, probabilmente stai chiedendo sul sito sbagliato. Se è il primo, potresti voler non respingere i suggerimenti sull'utilizzo di nuovi e rilevare errori così rapidamente ... Se la tua app è veramente "pesante", l'ultima cosa che desideri è un meccanismo di costruzione rovinato per rallentarlo. new
, nonostante tutto il flack che ottiene, è piuttosto veloce.