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.
la source
Make()
sansnew
raison Make est capitalisé et donc il suppose qu'il est un constructeurnew
? 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.Réponses:
Tout d'abord
arguments.callee
est obsolète dans ES5 strict, nous ne l'utilisons donc pas. La vraie solution est plutôt simple.Vous n'utilisez pas
new
du tout.C'est une bonne douleur dans le cul non?
Essayer
enhance
Maintenant, bien sûr, cela nécessite ES5, mais tout le monde utilise la cale ES5, n'est -ce pas?
Vous pouvez également être intéressé par des modèles alternatifs js OO
En passant, vous pouvez remplacer l'option deux par
Si vous voulez votre propre
Object.create
cale ES5, c'est très simplela source
Object.create
. Et avant ES5? ES5-Shim est répertoriéObject.create
comme DUBIOUS.__proto__
chose là-bas, alors nous sommes toujours sur le même point. Parce qu'avant ES5, il n'y a PAS de moyen plus simple de muter le prototype. Mais de toute façon, votre solution semble la plus élégante et tournée vers l'avenir. +1 pour cela. (ma limite de vote est atteinte)Object.create
cale est à peu près ma troisième solution mais moins compliquée et plus belle que la mienne bien sûr.La réponse évidente serait de ne pas oublier le
new
mot - clé.Vous changez la structure et le sens de la langue.
Ce qui, à mon avis, et pour le bien des futurs responsables de votre code, est une idée horrible.
la source
new
ou non, je trouverais celle-ci plus maintenable.;
instructions to end. (Insertion automatique des points-virgules)new
ou non est sémantiquement identique . Il n'y a aucun cas subtil où cet invariant est rompu. C'est pourquoi c'est bon et pourquoi vous voudriez l'utiliser.La solution la plus simple consiste à se souvenir
new
et à lancer une erreur pour rendre évident que vous avez oublié.Quoi que vous fassiez, ne l'utilisez pas
eval
. Je n'hésiterais pas à utiliser des propriétés non standard,__proto__
notamment parce qu'elles ne le sont pas et que leurs fonctionnalités peuvent changer.la source
.__proto__
c'est le diableJ'ai en fait écrit un article à ce sujet. http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html
Et vous pouvez même le généraliser pour ne pas avoir à ajouter cela en haut de chaque constructeur. Vous pouvez le voir en visitant mon post
Clause de non- responsabilité Je ne l'utilise pas dans mon code, je ne l'ai publié que pour la valeur didactique. J'ai trouvé qu'oublier un
new
est un bug facile à repérer. Comme d'autres, je ne pense pas que nous en ayons réellement besoin pour la plupart des codes. Sauf si vous écrivez une bibliothèque pour créer l'héritage JS, auquel cas vous pouvez utiliser à partir d'un seul endroit et vous utiliseriez déjà une approche différente de l'héritage direct.la source
var x = new Ctor();
et puis plus tard j'ai x asthis
et dovar y = Ctor();
, cela ne se comporterait pas comme prévu.this
", pouvez-vous peut-être poster un jsfiddle pour montrer le problème potentiel?Ctor.call(ctorInstance, 'value')
. Je ne vois pas de scénario valable pour ce que vous faites. Pour construire un objet, vous utilisez soitvar x = new Ctor(val)
ouvar y=Ctor(val)
. Même s'il y avait un scénario valide, je prétends que vous pouvez avoir des constructeurs sans utilisernew Ctor
, pas que vous pouvez avoir des constructeurs qui fonctionnent en utilisantCtor.call
Voir jsfiddle.net/JHNcR/2Que dis-tu de ça?
EDIT: j'ai oublié d'ajouter:
"Si votre application est vraiment" lourde ", la dernière chose que vous voulez, c'est un mécanisme de construction surchargé pour la ralentir"
Je suis absolument d'accord - lors de la création de «chose» ci-dessus sans le mot-clé «nouveau», il est plus lent / plus lourd qu'avec lui. Les erreurs sont votre ami, car elles vous disent ce qui ne va pas. De plus, ils disent à vos collègues développeurs ce qu'ils font de mal.
la source