web-dev-qa-db-fra.com

Pourquoi le débogueur Chrome) pense-t-il que la variable locale fermée n’est pas définie?

Avec ce code:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
  };
  bar();
}
baz();

J'obtiens ce résultat inattendu:

enter image description here

Quand je change le code:

function baz() {
  var x = "foo";

  function bar() {
    x;
    debugger;
  };
  bar();
}

Je reçois le résultat attendu:

enter image description here

De plus, s'il y a un appel à eval dans la fonction interne, je peux accéder à ma variable comme je le souhaite (peu importe ce que je passe à eval).

En attendant, 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'observe ce comportement depuis un certain temps, notamment la version 41.0.2272.43 beta (64 bits).

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ée dans la fonction interne, la variable x n’a toujours pas été définie.

Je comprends que l'utilisation d'un débogueur interactif présente souvent des bizarreries quant à la portée et à la définition des variables, mais il me semble que, sur la base de la spécification du langage, il devrait exister une "meilleure" solution à ces irrégularités. Je suis donc très curieux de savoir si cela est dû à Chrome optimisant plus loin que Firefox. Et aussi si ces optimisations peuvent facilement être désactivées pendant le développement (peut-être devraient-elles être désactivées lorsque les outils de développement sont ouverts ?).

De plus, je peux reproduire ceci avec des points d'arrêt ainsi que l'instruction debugger.

145
Gabe Kopley

J'ai trouvé un v8 problème qui concerne précisément ce que vous demandez.

Maintenant, pour résumer ce qui est dit dans ce rapport de problème ... la v8 peut stocker les variables locales à une fonction de la pile ou dans un " contexte "objet 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 any la 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 plutôt que sur la pile). Le cas de eval est spécial: si elle est appelée par une fonction interne, all, les variables locales sont placées dans l'objet contextuel.

La raison de l’objet de contexte est qu’en général, vous pouvez renvoyer une fonction interne à partir de la fonction externe et que la pile qui existait alors que la fonction externe s’exécutait ne sera plus disponible. Donc, tout ce que 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 sont sur la pile. Concernant le problème rencontré lors du débogage, un membre du projet dit :

La seule solution à laquelle je pouvais penser est que chaque fois que devtools est activé, nous supprimions tout le code et recompilions avec une allocation de contexte forcée. Cela réduirait considérablement les performances avec devtools activé.

Voici un exemple de "si une fonction interne fait référence à la variable, mettez-la dans un objet contextuel". Si vous exécutez ceci, vous pourrez accéder à x à l’instruction debugger même si x n’est utilisé que dans la fonction foo, qu’on n’appelle jamais!

function baz() {
  var x = "x value";
  var z = "z value";

  function foo () {
    console.log(x);
  }

  function bar() {
    debugger;
  };

  bar();
}
baz();
137
Louis

Comme @Louis, cela a été causé par les optimisations de la v8. Vous pouvez traverser Call stack pour encadrer où cette variable est visible:

call1call2

Ou remplacez debugger par

eval('debugger');

eval désoptera le morceau actuel

26
OwnageIsMagic

J'ai aussi remarqué cela dans nodejs. Je crois (et j’admets que cela n’est qu’une hypothèse) que lorsque le code est compilé, si x n'apparaît pas dans bar, il ne rend pas x disponible à l'intérieur la portée de bar. Cela le rend probablement un peu plus efficace; le problème est que quelqu'un a oublié (ou ne s'en souciait pas) que même s'il n'y avait pas de x dans bar, vous pourriez décider de lancer le débogueur et donc toujours avoir besoin d'accéder à x de l'intérieur bar.

6
David Knipe

Wow, vraiment intéressant!

Comme d'autres l'ont mentionné, cela semble être lié à scope, mais plus spécifiquement à debugger scope. Lorsque le script injecté est évalué dans les outils de développement, il semble déterminer un ScopeChain, ce qui entraîne une certaine bizarrerie (étant donné qu'il est lié à la portée de l'inspecteur/du débogueur). Une variation de ce que vous avez posté est la suivante:

(EDIT - en fait, vous en parlez dans votre question initiale, beurk, c'est grave! )

function foo() {
  var x = "bat";
  var y = "man";

  function bar() {
    console.log(x); // logs "bat"

    debugger; // Attempting to access "y" throws the following
              // Uncaught ReferenceError: y is not defined
              // However, x is available in the scopeChain. Weird!
  }
  bar();
}
foo();

Pour les ambitieux et/ou les curieux, cherchez (hé) la source pour voir ce qui se passe:

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspectorhttps://github.com/WebKit/webkit/tree/master/Source/ JavaScriptCore/débogueur

2
Jack

Je soupçonne que cela a à voir avec le levage variable et la fonction. 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'infos ici: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/ =

Je parie que Chrome appelle le point de rupture avec la variable non disponible pour l'étendue car il n'y a rien d'autre dans la fonction. Cela semble fonctionner:

function baz() {
  var x = "foo";

  function bar() {
    console.log(x); 
    debugger;
  };
  bar();
}

Comme le fait ceci:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
    console.log(x);     
  };
  bar();
}

J'espère que cela et/ou le lien ci-dessus peut vous aider. Ce sont mon genre préféré de SO questions, BTW :)

0
markle976