(1, eval) ('this') vs eval ('this') en JavaScript?

85

Je commence à lire les modèles JavaScript , certains codes me confondent.

var global = (function () {
    return this || (1, eval)('this');
}());

Voici mes questions:

Q1:

(1, eval) === eval?

Pourquoi et comment ça marche?

Q2: pourquoi pas simplement

var global = (function () {
    return this || eval('this');
}());

ou

 var global = (function () {
    return this;
}());
Shawjia
la source
J'ai mis à jour le titre car c'est un cas particulier. De plus, les parenthèses pour le type spécifique de crochets : [] et {} sont complètement différents :)

Réponses:

104

La différence entre (1,eval)et plain old evalest que le premier est une valeur et le second est une lvalue. Ce serait plus évident s'il s'agissait d'un autre identifiant:

var x;
x = 1;
(1, x) = 1; //  syntax error, of course!

C'est (1,eval)une expression qui cède eval(comme dire, (true && eval)ou (0 ? 0 : eval)voudrait), mais ce n'est pas une référence à eval.

Qu'est-ce que tu en as à faire?

Eh bien, la spécification Ecma considère une référence à evalêtre un « appel eval directe », mais une expression qui donne simplement evalêtre un effet indirect - et les appels eval indirects sont garantis pour exécuter une portée mondiale.

Des choses que je ne sais toujours pas:

  1. Dans quelles circonstances un appel d'évaluation directe ne s'exécute-t-il pas dans une portée globale?
  2. Dans quelles circonstances le thisd'une fonction à portée globale ne peut-il pas produire l'objet global?

Quelques informations supplémentaires peuvent être glanées ici .

ÉDITER

Apparemment, la réponse à ma première question est «presque toujours». Un direct evals'exécute à partir de la portée actuelle . Considérez le code suivant:

var x = 'outer';
(function() {
  var x = 'inner';
  eval('console.log("direct call: " + x)'); 
  (1,eval)('console.log("indirect call: " + x)'); 
})();

Sans surprise (heh-heh), ceci imprime:

direct call: inner
indirect call: outer

ÉDITER

Après plus d'expérimentation, je vais provisoirement dire que thiscela ne peut pas être réglé sur nullou undefined. Il peut être défini sur d'autres valeurs fausses (0, ``, NaN, false), mais seulement très délibérément.

Je vais dire que votre source souffre d'une inversion cranio-rectale légère et réversible et que vous voudrez peut-être envisager de passer une semaine à programmer à Haskell.

Malvolio
la source
3
Wow, je ne savais pas tout valuecontre lvaluetout (enfin, en pratique peut-être, mais pas en mots). Ni les règles d'évaluation ES5 (pas que je devrais raisonnablement avoir besoin d'utiliser evaljamais). Merci!
Stoive
Oui, evala beaucoup d'arêtes vives et désagréables et ne doit être utilisé qu'en dernier recours, puis, très, très soigneusement.
Malvolio
Je n'ai rencontré qu'une seule fois une utilisation valide - en évaluant une balise de script qui avait été ajoutée au DOM viainnerHtml
Stoive
1
lvalue n'a pas grand-chose à voir avec la détermination de l'évaluation directe car il fait généralement référence à l'expression qui peut apparaître sur le côté gauche d'une affectation, d'où le nom lvalue par opposition à rvalue. Un appel à eval est direct uniquement dans les conditions énumérées au 15.1.2.1.1 de la spécification qui indique que l'identificateur doit être evalet être la partie MemberExpression d'une CallExpression et faire référence à la evalfonction standard .
chuckj
1
@Malvolio Vous semblez impliquer que les lvalues ​​ont quelque chose à voir avec l'évaluation directe ou indirecte, ce qui n'est pas le cas. L'utilisation d'un identifiant appelé evalcomme cible d'une expression d'appel est particulière. Vous déclarez que l'ECMA traite la référence comme evalspéciale, ce qu'elle ne fait pas. C'est le placement dans l'expression d'appel qui est spécial et que l'expression évalue en evalfonction de la fonction standard . Par exemple, var eval = window.eval; eval('1');est toujours une évaluation directe et window.eval('1')n'est pas même si eval est une lvalue dans ce cas également.
chuckj
33

Le fragment,

var global = (function () {  
    return this || (1, eval)('this');  
}());  

évaluera correctement l'objet global même en mode strict. En mode non strict, la valeur de thisest l'objet global, mais en mode strict, elle l'est undefined. L'expression (1, eval)('this')sera toujours l'objet global. La raison en est les règles concernant les vers indirects directs eval. Les appels directs à evalont la portée de l'appelant et la chaîne thisserait évaluée à la valeur de thisdans la fermeture. Les evals indirects évaluent dans la portée globale comme s'ils étaient exécutés à l'intérieur d'une fonction dans la portée globale. Puisque cette fonction n'est pas elle-même une fonction en mode strict, l'objet global est passé en tant que this, puis l'expression 'this's'évalue en objet global. L'expression (1, eval)est juste une façon élégante de forcer leeval pour être indirect et renvoyer l'objet global.

A1: (1, eval)('this')n'est pas la même chose qu'en eval('this')raison des règles spéciales concernant les appels directs indirects vers eval.

A2: L'original fonctionne en mode strict, les versions modifiées ne le font pas.

chuckj
la source
12

À Q1:

Je pense que c'est un bon exemple d'opérateur virgule dans JS. J'aime l'explication de l'opérateur virgule dans cet article: http://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/

L'opérateur virgule évalue ses deux opérandes (de gauche à droite) et renvoie la valeur du deuxième opérande.

À Q2:

(1, eval)('this')est considéré comme un appel eval indirect, qui dans ES5 exécute le code globalement. Le résultat sera donc le contexte global.

Voir http://perfectionkills.com/global-eval-what-are-the-options/#evaling_in_global_scope

Grace Shao
la source
7

Q1: Plusieurs instructions javascript consécutives séparées par une virgule prennent la valeur de la dernière instruction. Donc:

(1, eval)prend la valeur du dernier qui est une référence de fonction à la eval()fonction. Il le fait apparemment de cette façon pour transformer l' eval()appel en un appel d'évaluation indirect qui sera évalué dans la portée globale dans ES5. Détails expliqués ici .

Q2: Il doit y avoir un environnement qui ne définit pas un global this, mais qui définit eval('this'). C'est la seule raison à laquelle je peux penser.

jfriend00
la source
Peut-être que quelqu'un essaie d'esquiver un crochet d'enregistrement qui refuse /eval\(/g?
Stoive le
@Stoive - ouais, je me suis aussi demandé quelque chose comme ça. Si ce n'est pas un crochet d'enregistrement, certains filtrent quelque part dans le processus (peut-être la minimisation).
jfriend00
7
Cela a quelque chose à voir avec le mode strict ES5. AFAIK en mode strict ES5 tout evalcode est exécuté dans son propre contexte plutôt que dans le contexte global ou le contexte englobant. Une façon de contourner cela est de le référencer indirectement comme le fait le code en question.
Cristian Sanchez
1
Jetez un œil à " Global eval. Quelles sont les options? ".
Saxoier
J'ai mis à jour ma réponse pour inclure les informations de CDSanchez et @Saxoier. Merci.
jfriend00