Avec ce code:
function baz() {
var x = "foo";
function bar() {
debugger;
};
bar();
}
baz();
J'obtiens ce résultat inattendu:
Quand je change le code:
function baz() {
var x = "foo";
function bar() {
x;
debugger;
};
bar();
}
J'obtiens le résultat attendu:
De plus, s'il y a un appel à eval
l'intérieur de la fonction interne, je peux accéder à ma variable comme je veux (peu importe ce à quoi je passe eval
).
Pendant ce temps, les outils de développement de Firefox donnent le comportement attendu dans les deux cas.
Que se passe-t-il avec Chrome que le débogueur se comporte moins facilement que Firefox? J'ai observé ce comportement pendant un certain temps, jusqu'à la version 41.0.2272.43 bêta (64 bits) incluse.
Est-ce que le moteur javascript de Chrome "aplatit" les fonctions quand il le peut?
Fait intéressant si j'ajoute une deuxième variable qui est référencé dans la fonction intérieure, la x
variable est encore mal défini.
Je comprends qu'il y a souvent des bizarreries avec la portée et la définition de variable lors de l'utilisation d'un débogueur interactif, mais il me semble que sur la base de la spécification du langage, il devrait y avoir une «meilleure» solution à ces bizarreries. Je suis donc très curieux de savoir si cela est dû à l'optimisation de Chrome plus loin que Firefox. Et aussi si ces optimisations peuvent ou non être facilement désactivées pendant le développement (peut-être devraient-elles être désactivées lorsque les outils de développement sont ouverts?).
En outre, je peux reproduire cela avec des points d'arrêt ainsi que la debugger
déclaration.
la source
debugger;
ligne n'est pas réellement appelée de l'intérieurbar
. Regardez donc la trace de la pile lorsqu'elle s'arrête dans le débogueur: labar
fonction est-elle mentionnée dans la trace de pile? Si j'ai raison, alors le stacktrace devrait indiquer qu'il est mis en pause à la ligne 5, à la ligne 7, à la ligne 9.temp1
est attachée à la console et vous pouvez l'utiliser pour accéder à l'entrée d'étendue.Réponses:
J'ai trouvé un rapport de problème v8 qui concerne précisément ce que vous demandez.
Maintenant, pour résumer ce qui est dit dans ce rapport de problème ... v8 peut stocker les variables qui sont locales à une fonction sur la pile ou dans un objet "context" qui vit sur le tas. Il allouera des variables locales sur la pile tant que la fonction ne contient aucune fonction interne qui y fait référence. C'est une optimisation . Si une fonction interne fait référence à une variable locale, cette variable sera placée dans un objet de contexte (c'est-à-dire sur le tas au lieu de sur la pile). Le cas de
eval
est particulier: s'il est appelé par une fonction interne, toutes les variables locales sont placées dans l'objet context.La raison de l'objet context est qu'en général, vous pouvez renvoyer une fonction interne à partir de la fonction externe, puis la pile qui existait pendant l'exécution de la fonction externe ne sera plus disponible. Ainsi, tout ce à quoi la fonction interne accède doit survivre à la fonction externe et vivre sur le tas plutôt que sur la pile.
Le débogueur ne peut pas inspecter ces variables qui se trouvent sur la pile. Concernant le problème rencontré lors du débogage, un membre du projet dit :
Voici un exemple de "si une fonction interne fait référence à la variable, placez-la dans un objet de contexte". Si vous exécutez ceci, vous pourrez accéder
x
à l'debugger
instruction même si ellex
n'est utilisée que dans lafoo
fonction, qui n'est jamais appelée !la source
Comme @Louis l'a dit, cela est dû aux optimisations v8. Vous pouvez parcourir la pile des appels vers le cadre où cette variable est visible:
Ou remplacer
debugger
pareval
désactive le bloc actuella source
debugger
, et le contexte est en effet disponible. Si vous augmentez d'un niveau la pile vers le code que vous essayez réellement de déboguer, vous n'avez plus accès au contexte. Donc, c'est juste un peu maladroit, ne pas pouvoir regarder le code que vous déboguez tout en accédant aux variables de fermeture cachées. Je vais cependant voter pour, car cela m'évite d'avoir à ajouter du code qui n'est évidemment pas destiné au débogage, et cela me donne accès à tout le contexte sans désoptimiser toute l'application.eval
fenêtre source jaune ed pour accéder au contexte: vous ne pouvez pas parcourir le code (à moins que vous ne mettiezeval('debugger')
entre toutes les lignes que vous souhaitez parcourir.)controllers.forEach(c => c.update())
et j'ai atteint un point d'arrêt quelque part au fond de moic.update()
. Si je sélectionne ensuite le cadre oùcontrollers.forEach()
est appelé, ilcontrollers
n'est pas défini (mais tout le reste de ce cadre est visible). Je ne pourrais pas reproduire avec une version minimale, je suppose qu'il peut y avoir un certain seuil de complexité à franchir ou quelque chose du genre.somewhere deep inside c.update()
votre code devient asynchrone et vous voyez un cadre de pile asynchroneJ'ai également remarqué cela dans nodejs. Je crois (et j'admets que ce n'est qu'une supposition) que lorsque le code est compilé, s'il
x
n'apparaît pas à l'intérieurbar
, il ne le rend pasx
disponible dans la portée debar
. Cela le rend probablement un peu plus efficace; le problème est que quelqu'un a oublié (ou s'en moque) que même s'il n'y a pas d'x
entréebar
, vous pourriez décider d'exécuter le débogueur et donc toujours avoir besoin d'accéderx
de l'intérieurbar
.la source
eval
commande non liée . Si la variable est déclarée, elle doit être accessible.Wow, vraiment intéressant!
Comme d'autres l'ont mentionné, cela semble être lié
scope
, mais plus spécifiquement, lié àdebugger scope
. Lorsque le script injecté est évalué dans les outils de développement, il semble déterminer aScopeChain
, ce qui entraîne une certaine bizarrerie (car il est lié à la portée de l'inspecteur / débogueur). Voici une variante de ce que vous avez publié:(EDIT - en fait, vous mentionnez cela dans votre question initiale, ouais, mon mauvais! )
Pour les ambitieux et / ou curieux, explorez (heh) la source pour voir ce qui se passe:
https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger
la source
Je soupçonne que cela a à voir avec le levage de variables et de fonctions. JavaScript amène toutes les déclarations de variables et de fonctions en haut de la fonction dans laquelle elles sont définies. Plus d'informations ici: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/
Je parie que Chrome appelle le point de rupture avec la variable indisponible pour la portée car il n'y a rien d'autre dans la fonction. Cela semble fonctionner:
Comme ça:
J'espère que cela et / ou le lien ci-dessus vous aidera. Ce sont mes types de questions SO préférées, BTW :)
la source