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

167

С этим кодом:

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

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

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

введите описание изображения здесь

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

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

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

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

введите описание изображения здесь

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

Между тем, инструменты разработки Firefox дают ожидаемое поведение в обоих случаях.

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

Это то, что движок Chrome javascript «выравнивает» функции, когда это возможно?

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

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

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

Гейб Копли
источник
2
может быть, это
избавляет
markle976, кажется, говорит, что debugger;линия на самом деле не вызывается изнутри bar. Итак, посмотрите на трассировку стека, когда она останавливается в отладчике: barупоминается ли функция в трассировке стека? Если я прав, то трассировка стека должна сказать, что она приостановлена ​​в строке 5, в строке 7, в строке 9.
David Knipe
Я не думаю, что это как-то связано с функциями выравнивания V8. Я думаю, что это просто причуда; Я не знаю, могу ли я назвать это ошибкой. Я думаю, что ответ Дэвида ниже имеет смысл.
markle976
2
У меня та же проблема, я ненавижу это. Но когда мне нужно иметь записи о закрытии доступа в консоли, я иду туда, где вы можете увидеть область действия, найти запись о закрытии и открыть ее. Затем щелкните правой кнопкой мыши по нужному элементу и выберите Сохранить как глобальную переменную . Новая глобальная переменная temp1присоединена к консоли, и вы можете использовать ее для доступа к записи области.
Пабло

Ответы:

149

Я нашел отчет о проблеме 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();
Луис
источник
13
Вы нашли способ деактивировать код? Мне нравится использовать отладчик в качестве REPL и код, а затем перенести код в мои собственные файлы. Но это часто невозможно, так как переменные, которые должны быть там, недоступны. Простой eval не сделает этого. Я слышу бесконечную петлю мощи.
Рэй Фосс
На самом деле я не сталкивался с этой проблемой во время отладки, поэтому я не искал способы деактивировать код.
Луи
6
Последний комментарий к проблеме гласит: перевод V8 в режим, где все принудительно выделяется из контекста, возможен, но я не уверен, как / когда вызывать это через пользовательский интерфейс Devtools. Для отладки я иногда хотел бы сделать это. , Как я могу форсировать такой режим?
Сума
2
@ user208769 Закрывая как дубликат, мы предпочитаем вопрос, который является наиболее полезным для будущих читателей. Существует несколько факторов, которые помогают определить, какой вопрос является наиболее полезным: на ваш вопрос было получено ровно 0 ответов, а на этот ответ было получено несколько ответов. Таким образом, этот вопрос является наиболее полезным из двух. Финики становятся определяющим фактором только в том случае, если полезность в основном одинакова.
Луи
1
Этот ответ отвечает на реальный вопрос (почему?), Но подразумеваемый вопрос - как получить доступ к неиспользуемым переменным контекста для отладки без добавления дополнительных ссылок на них в моем коде? - лучше ответил @OwnageIsMagic ниже.
Зигфрид
30

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

call1 вызов 2

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

eval('debugger');

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

OwnageIsMagic
источник
1
Почти отлично! Он останавливается в модуле VM (желтый) с содержимым debugger, и контекст действительно доступен. Если вы поднимаетесь на один уровень вверх по стеку к коду, который на самом деле пытаетесь отлаживать, вы возвращаетесь к отсутствию доступа к контексту. Так что это немного неуклюже, потому что вы не можете смотреть на код, который вы отлаживаете, при доступе к скрытым переменным замыкания. Тем не менее, я буду одобрять, поскольку это избавляет меня от необходимости добавлять код, который явно не предназначен для отладки, и дает мне доступ ко всему контексту без деоптимизации всего приложения.
Зигфрид
Ох ... это даже более глупо, чем использование желтого evalокна исходного текста для получения доступа к контексту: вы не можете пошагово проходить код (если не вставляете eval('debugger')между всеми строками, которые хотите пройти.)
Зигфрид
Кажется, что существуют ситуации, когда определенные переменные невидимы даже после перехода к соответствующему кадру стека; У меня есть что-то вроде, controllers.forEach(c => c.update())и я достиг точки останова где-то глубоко внутри c.update(). Если я затем выберу кадр, где он controllers.forEach()вызывается, controllersон не определен (но все остальное в этом кадре видно). Я не мог воспроизвести с минимальной версией, я предполагаю, что может быть некоторый порог сложности, который должен быть пройден, или что-то в этом роде.
PeterT
@PeterT, если <undefined> вы находитесь не в том месте или somewhere deep inside c.update()ваш код работает асинхронно и вы видите кадр асинхронного стека
OwnageIsMagic
6

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

Дэвид Кнайп
источник
3
Спасибо. По сути, я хочу объяснить новичкам JavaScript лучше, чем «Отладчик лжи».
Гейб Копли
@GabeKopley: Технически отладчик не лжет. Если на переменную нет ссылки, то она технически не заключена. Таким образом, интерпретатору не нужно создавать замыкание.
slebetman
7
Не в этом дело. При использовании отладчика я часто бывал в ситуации, когда хотел узнать значение переменной во внешней области видимости, но не мог из-за этого. И на более философской ноте, я бы сказал, что отладчик лжет. То, существует ли переменная во внутренней области видимости, не должно зависеть от того, используется ли она на самом деле или есть не связанная evalкоманда. Если переменная объявлена, она должна быть доступна.
Дэвид Книп
2

Вау, действительно интересно!

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

(РЕДАКТИРОВАТЬ - на самом деле, вы упоминаете об этом в своем первоначальном вопросе, да , мой плохой! )

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

разъем
источник
0

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

Могу поспорить, что Chrome вызывает точку останова с переменной, недоступной для области, потому что в функции больше ничего нет. Это похоже на работу:

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

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

Как это сделать:

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

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

Надеюсь, что и / или ссылка выше помогает. Это мой любимый вид ТАК вопросов, кстати :)

markle976
источник
Спасибо! :) Мне интересно, что FF делает по-другому. С моей точки зрения, как разработчика, опыт FF объективно лучше ...
Гейб Копли
2
«вызывая точку останова в lex time», я сомневаюсь в этом. Это не то, что точки останова для. И я не понимаю, почему отсутствие других вещей в функции должно иметь значение. Тем не менее, если это что-то вроде nodejs, то точки останова могут быть очень ошибочными.
Дэвид Книп