J'ai trouvé quelque chose d'étrange concernant le mode strict en Javascript.
use strict
du tout, etarguments.callee
.use strict
dans mon propre code, dans une fonction.Lorsque j'appelle l'une des fonctions fournies par la bibliothèque, cela génère une erreur. cependant,
use strict
J'ai supprimé tous les éléments non liés et réduit le code dans ce ( démo en ligne sur jsFiddle ):
// This comes from the minified external JS library.
// It creates a global object "foo".
(function () {
foo = {};
foo.bar = function (e) {
return function () {
var a5 = arguments.callee;
while (a5) {
a5 = a5.caller // Error on this line in all browsers except Chrome
}
}
}("any value here");
})();
// Here's my code.
(function() {
"use strict"; // I enable strict mode in my own function only.
foo.bar();
alert("done");
})();
+-----------------------+-----+--------------------------------------------------------------+
| Browser | OS | Error |
+-----------------------+-----+--------------------------------------------------------------+
| Chrome 27.0.1453.94 m | Win | <<NO ERROR!>> |
| Opera 12.15 | Win | Unhandled Error: Illegal property access |
| Firefox 21.0 | Win | TypeError: access to strict mode caller function is censored |
| Safari 5.1.7 | Win | TypeError: Type error |
| IE 10 | Win | SCRIPT5043: Accessing the 'caller' property of a function or |
| | | arguments object is not allowed in strict mode |
| Chrome 27.0.1543.93 | Mac | <<NO ERROR!>> |
| Opera 12.15 | Mac | Unhandled Error: Illegal property access |
| Firefox 21.0 | Mac | TypeError: access to strict mode caller function is censored |
| Safari 6.0.4 | Mac | TypeError: Function.caller used to retrieve strict caller |
+-----------------------+-----+--------------------------------------------------------------+
Remarque: pour OS
, Win
= Windows 7, Mac
= Mac OS 10.7.5
use strict
(voir Puis-je utiliser ).use strict
est limité à ma fonction, donc tout ce qui est défini en dehors de sa portée n'est pas affecté (voir cette question de débordement de pile ).Donc, tous les navigateurs sont-ils sauf Chrome faux? Ou est-ce l'inverse? Ou est-ce un comportement indéfini pour que les navigateurs puissent choisir de l'implémenter de l'une ou l'autre façon?
Quelques points rapides avant d'entrer dans le vif du sujet:
- Tous les navigateurs de bureau modernes prennent en charge
use strict
...
Non pas du tout. IE8 est un navigateur assez moderne (plus maintenant, en 2015), et IE9 est un assez navigateur assez moderne. Aucun d'eux ne prend en charge le mode strict (IE9 en prend en charge certaines parties). IE8 va être avec nous pendant longtemps, car il est aussi élevé que possible sur Windows XP. Même si XP est maintenant complètement en fin de vie (enfin, vous pouvez acheter un plan spécial "Support personnalisé" de MS), les gens continueront de l'utiliser pendant un certain temps.
- Le
use strict
Est délimité dans ma fonction, donc tout ce qui est défini en dehors de sa portée n'est pas affecté
Pas assez. La spécification impose des restrictions sur la façon dont même le code non strict utilise des fonctions créées en mode strict. Le mode strict peut donc sortir de sa boîte. Et en fait, cela fait partie de ce qui se passe avec le code que vous utilisez.
Donc, tous les navigateurs sauf Chrome est-il faux? Ou est-ce l'inverse? Ou est-ce un comportement indéfini pour que les navigateurs puissent choisir de l'implémenter de l'une ou l'autre façon?
En y regardant un peu, on dirait:
Chrome fait les choses dans un sens,
Firefox fait les choses différemment,
... et IE10 l'obtient très légèrement faux. :-) (IE9 se trompe définitivement, mais pas de manière particulièrement nuisible.)
Je n'ai pas regardé les autres, j'ai pensé que nous avions recouvert le sol.
Le code qui cause fondamentalement le problème est cette boucle
var a5 = arguments.callee;
while (a5) {
a5 = a5.caller // Error on this line in all browsers except Chrome
}
... qui repose sur la propriété caller
des objets fonction. Commençons donc par là.
Function#caller
La propriété Function#caller
N'a jamais été définie dans la spécification de 3e édition. Certaines implémentations l'ont fourni, d'autres non. Ses une mauvaise idée choquante (désolé, c'était subjectif, n'est-ce pas?) un problème d'implémentation (encore plus d'un que arguments.caller
), en particulier dans les environnements multithreads (et il existe plusieurs (moteurs JavaScript filetés), ainsi qu'avec du code récursif, comme l'a souligné Bergi dans les commentaires sur la question.
Donc, dans la 5e édition, ils s'en sont explicitement débarrassés, en spécifiant que référencer la propriété caller
sur une fonction stricte déclencherait une erreur. (C'est dans §13.2, Création d'objets fonction, étape 19 .)
C'est sur une fonction stricte. Sur une fonction non stricte, cependant, le comportement n'est pas spécifié et dépend de l'implémentation. C'est pourquoi il existe tant de façons différentes de faire les choses correctement.
Il est plus facile de se référer au code instrumenté qu’une session de débogage, alors tilisez ceci :
console.log("1. Getting a5 from arguments.callee");
var a5 = arguments.callee;
console.log("2. What did we get? " +
Object.prototype.toString.call(a5));
while (a5) {
console.log("3. Getting a5.caller");
a5 = a5.caller; // Error on this line in all browsers except Chrome
console.log("4. What is a5 now? " +
Object.prototype.toString.call(a5));
}
Sur V8 (moteur JavaScript de Chrome), le code ci-dessus nous donne ceci:
1. Obtenir a5 à partir d'arguments.callee 2. Qu'avons-nous obtenu? [Fonction d'objet] 3. Obtenir a5.caller 4. Qu'est-ce que l'A5 maintenant? [objet Null]
Nous avons donc obtenu une référence à la fonction foo.bar
De arguments.callee
, Mais accéder à caller
sur cette fonction non stricte nous a donné null
. La boucle se termine et nous n'obtenons aucune erreur.
Puisque Function#caller
N'est pas spécifié pour les fonctions non strictes, V8 est autorisé à faire tout ce qu'il veut pour cet accès à caller
sur foo.bar
. Renvoyer null
est parfaitement raisonnable (même si j'ai été surpris de voir null
plutôt que undefined
). (Nous y reviendrons null
dans les conclusions ci-dessous ...)
SpiderMonkey (le moteur JavaScript de Firefox) fait ceci:
1. Obtenir a5 à partir d'arguments.callee 2. Qu'avons-nous obtenu? [Fonction d'objet] 3. Obtenir a5.caller TypeError: l'accès à la fonction d'appel en mode strict est censuré
Nous commençons par obtenir foo.bar
De arguments.callee
, Mais l'accès à caller
sur cette fonction non stricte échoue avec une erreur.
Puisque, encore une fois, l'accès à caller
sur une fonction non stricte est un comportement non spécifié, les gens de SpiderMonkey peuvent faire ce qu'ils veulent. Ils ont décidé de lancer une erreur si la fonction qui serait retournée est une fonction stricte. Une ligne fine, mais comme cela n'est pas spécifié, ils sont autorisés à marcher.
JScript (moteur JavaScript d'IE10) fait ceci:
1. Obtenir a5 de arguments.callee 2. Qu'avons-nous obtenu? [Fonction objet] 3. Obtenir a5.caller SCRIPT5043: L'accès à la propriété 'caller' d'une fonction ou d'un objet arguments n'est pas autorisé en mode strict
Comme pour les autres, nous obtenons la fonction foo.bar
De arguments.callee
. Ensuite, essayer d'accéder à caller
de cette fonction non stricte nous donne une erreur disant que nous ne pouvons pas le faire en mode strict.
J'appelle cela "mauvais" (mais avec un très minuscule "w") parce qu'il dit que nous ne pouvons pas faire ce que nous faisons en mode strict, mais nous ne sommes pas in mode strict.
Mais vous pourriez dire que ce n'est pas plus faux que ce que Chrome et Firefox font, car (encore) l'accès caller
est un comportement non spécifié. Les gens d'IE10 ont donc décidé que leur implémentation de ce comportement non spécifié jetterait une erreur en mode strict. Je pense que c'est trompeur, mais encore une fois, si c'est "faux", ce n'est certainement pas très faux.
BTW, IE9 se trompe définitivement:
1. Obtenir a5 à partir d'arguments.callee 2. Qu'avons-nous obtenu? [Fonction d'objet] 3. Obtenir a5.caller 4. Qu'est-ce que l'A5 maintenant? [Fonction d'objet] 3. Obtenir a5.caller 4. Qu'est-ce que l'A5 maintenant? [objet Null]
Il autorise Function#caller
Sur la fonction non stricte, puis l'autorise sur une fonction stricte, renvoyant null
. La spécification est claire que ce deuxième accès aurait dû générer une erreur, car il accédait à caller
sur une fonction stricte.
Ce qui est intéressant à propos de tout ce qui précède, c'est qu'en plus du comportement clairement spécifié de lancer une erreur si vous essayez d'accéder à caller
sur des fonctions strictes, Chrome, Firefox et IE10, tous (de diverses manières) empêchent votre en utilisant caller
pour obtenir une référence à une fonction stricte, même lorsque vous accédez à caller
sur une fonction non stricte. Firefox le fait en lançant une erreur. Chrome et IE10 le font en renvoyant null
. Ils prennent tous en charge l'obtention d'une référence à un non -strict fonction via caller
(sur une fonction non stricte), mais pas une fonction stricte.
Je ne peux trouver ce comportement spécifié nulle part (mais alors, caller
sur les fonctions non strictes est entièrement non spécifié ...). C'est probablement la bonne chose(tm), Je ne le vois pas spécifié.
Ce code est également amusant à jouer avec: Live Copy | Source en direct
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Strict and Loose Function#caller</title>
<style>
p {
font-family: sans-serif;
margin: 0.1em;
}
.err {
color: #d00;
}
</style>
</head>
<body>
<script>
function display(msg, cls) {
var p = document.createElement('p');
if (cls) {
p.className = cls;
}
p.innerHTML = String(msg);
document.body.appendChild(p);
}
// The loose functions
(function () {
function loose1() {
display("loose1 calling loose2");
loose2();
}
loose1.id = "loose1"; // Since name isn't standard yet
function loose2() {
var c;
try {
display("loose2: looping through callers:");
c = loose2;
while (c) {
display("loose2: getting " + c.id + ".caller");
c = c.caller;
display("loose2: got " +
((c && c.id) || Object.prototype.toString.call(c)));
}
display("loose2: done");
}
catch (e) {
display("loose2: exception: " +
(e.message || String(e)),
"err");
}
}
loose2.id = "loose2";
window.loose1 = loose1;
window.loose2 = loose2;
})();
// The strict ones
(function() {
"use strict";
function strict1() {
display("strict1: calling strict2");
strict2();
}
strict1.id = "strict1";
function strict2() {
display("strict2: calling loose1");
loose1();
}
strict2.id = "strict2";
function strict3() {
display("strict3: calling strict4");
strict4();
}
strict3.id = "strict3";
function strict4() {
var c;
try {
display("strict4: getting strict4.caller");
c = strict4.caller;
}
catch (e) {
display("strict4: exception: " +
(e.message || String(e)),
"err");
}
}
strict4.id = "strict4";
strict1();
strict3();
})();
</script>
</body>
</html>
Je dois utiliser une ancienne bibliothèque Telerik JS que je ne peux pas facilement mettre à jour et j'ai rencontré cette erreur ce matin. Une solution de contournement possible pour certaines personnes pourrait être d'utiliser la fonction JS 'setTimeout' pour quitter le mode strict avant d'appeler la fonction de mode lâche.
par exemple. Change ça:
function functionInStrictMode(){
looseModeFunction();
}
Pour quelque chose comme ça:
function functionInStrictMode(){
setTimeout(looseModeFunction);
}
Je suppose que cela fonctionne probablement parce que setTimeout rétablit le contexte dans l'espace de noms global et/ou laisse la portée de functionInStrictMode. Je ne comprends pas bien tous les détails. Il pourrait y avoir de meilleures façons; Je n'ai pas fait de recherche approfondie sur ce sujet, mais j'ai pensé le publier ici pour discussion.