Attention: c'est un long post.
Restons simples. Je veux éviter d'avoir à préfixer le nouvel opérateur chaque fois que j'appelle un constructeur en JavaScript. C'est parce que j'ai tendance à l'oublier et que mon code se fout mal.
La façon simple de contourner cela est la suivante ...
function Make(x) {
if ( !(this instanceof arguments.callee) )
return new arguments.callee(x);
// do your stuff...
}
Mais, j'en ai besoin pour accepter la variable no. d'arguments, comme ça ...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
La première solution immédiate semble être la méthode «appliquer» comme celle-ci ...
function Make() {
if ( !(this instanceof arguments.callee) )
return new arguments.callee.apply(null, arguments);
// do your stuff
}
C'est faux cependant - le nouvel objet est passé à la apply
méthode et NON à notre constructeur arguments.callee
.
Maintenant, j'ai trouvé trois solutions. Ma question simple est: laquelle semble la meilleure. Ou, si vous avez une meilleure méthode, dites-le.
Premier - utilisez eval()
pour créer dynamiquement du code JavaScript qui appelle le constructeur.
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...
}
Deuxièmement - Chaque objet a une __proto__
propriété qui est un lien «secret» vers son objet prototype. Heureusement, cette propriété est accessible en écriture.
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;
}
Troisième - C'est quelque chose de similaire à la deuxième solution.
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 solution semble maladroite et s'accompagne de tous les problèmes de "evil eval".__proto__
La solution n'est pas standard et le «grand navigateur de mIsERY» ne l'honore pas.La troisième solution semble trop compliquée.
Mais avec les trois solutions ci-dessus, nous pouvons faire quelque chose comme ça, que nous ne pouvons pas autrement ...
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();
Donc, effectivement, les solutions ci-dessus nous donnent de "vrais" constructeurs qui acceptent la variable no. d'arguments et ne nécessitent pas new
. Quel est votre avis là-dessus.
-- MISE À JOUR --
Certains ont dit "jette juste une erreur". Ma réponse est: nous faisons une application lourde avec plus de 10 constructeurs et je pense que ce serait beaucoup plus maniable si chaque constructeur pouvait gérer "intelligemment" cette erreur sans lancer de messages d'erreur sur la console.
Make()
sans new
raison Make est capitalisé et donc il suppose qu'il est un constructeur
new
? Parce que si c'est le dernier, vous demandez probablement sur le mauvais site. Si c'est le premier, vous voudrez peut-être ne pas rejeter les suggestions concernant l'utilisation de nouvelles erreurs et la détection si rapide ... Si votre application est vraiment "lourde", la dernière chose que vous voulez est un mécanisme de construction surchargé pour la ralentir. new
, malgré tout le flack qu'il obtient, est assez rapide.