Почему отладчик Chrome считает закрытую локальную переменную undefined?

С помощью этого кода:

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

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

Я получаю этот неожиданный результат:

enter image description here

Когда я меняю код:

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

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

Я получаю ожидаемый результат:

enter image description here

Кроме того, если во внутренней функции есть вызов eval, я могу получить доступ к моей переменной, как я хочу сделать (не имеет значения, что я передаю eval).

Тем временем, инструменты Firefox dev дают ожидаемое поведение в обоих обстоятельствах.

Что с Chrome, что отладчик ведет себя менее удобно, чем Firefox? Я наблюдал это поведение некоторое время, вплоть до версии 41.0.2272.43 beta (64-разрядная версия).

Является ли это тем, что механизм javascript Chrome "сглаживает" функции, когда это возможно?

Интересно, если добавить вторую переменную, на которую ссылаются во внутренней функции, переменная x все еще undefined.

Я понимаю, что при использовании интерактивного отладчика часто возникают причуды с определением области видимости и переменной, но мне кажется, что на основе спецификации языка должно быть "лучшее" решение этих причуд. Поэтому мне очень любопытно, что это связано с тем, что Chrome оптимизирован дальше Firefox. А также можно ли легко отключить эти оптимизации во время разработки (возможно, они должны быть отключены при открытии инструментов dev?).

Кроме того, я могу воспроизвести это с точками останова, а также с инструкцией debugger.

Ответ 1

Я нашел отчет о выпуске v8 , который именно о том, что вы просите.

Теперь, чтобы суммировать то, что сказано в этом выпуске... v8 может хранить переменные, которые являются локальными для функции в стеке или в объекте "context", который живет в куче, Он будет выделять локальные переменные в стеке, пока функция не содержит никакой внутренней функции, которая относится к ним. Это оптимизация. Если какая-либо внутренняя функция относится к локальной переменной, эта переменная будет помещена в объект контекста (т.е. В куче, а не в стеке). Случай eval является особым: если он вообще вызван внутренней функцией, все локальные переменные помещаются в объект контекста.

Причиной для контекстного объекта является то, что в общем случае вы можете вернуть внутреннюю функцию из внешней, а затем стек, который существовал во время выполнения внешней функции, больше не будет доступен. Таким образом, все, что получает доступ к внутренней функции, должно пережить внешнюю функцию и жить в куче, а не в стеке.

Отладчик не может проверять те переменные, которые находятся в стеке. Что касается проблемы, возникающей при отладке, один участник проекта говорит:

Единственное решение, о котором я мог думать, это то, что всякий раз, когда отключается devtools, мы прекращаем весь код и перекомпилируем с принудительным распределением контекста. Это резко снизило бы производительность при помощи devtools, хотя.

Здесь пример "если какая-либо внутренняя функция относится к переменной, поместите ее в объект контекста". Если вы запустите это, вы сможете получить доступ к x в инструкции debugger, хотя x используется только в функции foo, которая никогда не вызывается!

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();

Ответ 2

Как сказал @Louis, это вызвано оптимизацией v8. Вы можете пройти стек вызовов к кадру, где эта переменная видна:

call1 call2

Или замените debugger на

eval('debugger');

eval откажется от текущего чанка

Ответ 3

Я также заметил это в nodejs. Я считаю (и я признаю, что это только предположение), что при компиляции кода, если x не появляется внутри bar, он не делает x доступным в пределах области bar. Это, вероятно, делает его несколько более эффективным; проблема заключается в том, что кто-то забыл (или не заботился), что даже если там нет x в bar, вы можете решить запустить отладчик и, следовательно, еще нужно получить доступ к x изнутри bar.

Ответ 4

Ничего себе, действительно интересно!

Как отмечали другие, это, по-видимому, связано с scope, но более конкретно, связано с debugger scope. Когда введенный script оценивается в инструментах разработчика, он, как представляется, определяет a ScopeChain, что приводит к некоторой причудливости (поскольку она связана с областью инспектора/отладчика). Вариант того, что вы разместили, это:

(EDIT - на самом деле, вы упомянули об этом в своем исходном вопросе, yikes, мне плохо!)

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();

Для амбициозного и/или любопытного масштаба (хе) из источника, чтобы узнать, что происходит:

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger

Ответ 5

Я подозреваю, что это связано с подъемом переменных и функций. JavaScript приносит все объявления переменных и функций в начало функции, в которой они определены. Дополнительная информация здесь: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

Я уверен, что Chrome вызывает точку останова с переменной, недоступной для области видимости, потому что в функции нет ничего другого. Кажется, что это работает:

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

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