Чому налагоджувач 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-бітної).

Це те, що двигун javascript у Chrome «розгладжує» функції, коли може?

Цікаво , якщо я додаю другу змінну , яка буде посилатися у внутрішній функції, то xзмінна ще НЕ визначена.

Я розумію, що часто використовуються примхи із визначенням обсягу та змінної при використанні інтерактивного налагоджувача, але мені здається, що на основі мовної специфікації повинно бути "найкращим" рішенням цих вигадок. Тож мені дуже цікаво, якщо це пов’язано з тим, що Chrome оптимізується далі, ніж Firefox. А також, можна легко відключити ці оптимізації під час розробки (можливо, їх слід було б відключити, коли відкриті інструменти розробки?).

Крім того, я можу відтворити це за допомогою точок прориву, а також debuggerзаяви.


2
можливо, це стане для вас невикористаними змінними з вашого шляху ...
dandavis

markle976, здається, говорить, що debugger;лінія насправді не викликається зсередини bar. Отже, подивіться на слід стека, коли він зупиняється в налагоджувачі: Чи barзгадується функція в стектейрі? Якщо я маю рацію, то стек-трек повинен сказати, що він призупинений у рядку 5, у рядку 7, у рядку 9.
Девід Найпе

Я не думаю, що це має нічого спільного з функціями вирівнювання V8. Я думаю, що це лише химерність; Я не знаю, чи б я навіть назвав це помилкою. Я думаю, що відповідь Давида нижче має найбільш сенс.
markle976


2
У мене те саме питання, я його ненавиджу. Але коли мені потрібно мати записи про закриття доступу в консолі, я заходжу туди, де можна побачити область, знайти запис закриття та відкрити його. Потім клацніть правою кнопкою миші потрібний елемент і клацніть на Зберегти як глобальну змінну . temp1До консолі додається нова глобальна змінна, і ви можете використовувати її для доступу до запису області.
Пабло

Відповіді:


149

Я знайшов звіт про проблему v8, який саме стосується того, що ви запитуєте.

Тепер, щоб узагальнити те, що сказано в цьому звіті про випуск ... v8 може зберігати змінні, які є локальними для функції в стеці або в "контекстному" об'єкті, що живе в купі. Він буде виділяти локальні змінні на стеку до тих пір, поки функція не містить жодної внутрішньої функції, яка посилається на них. Це оптимізація . Якщо будь-яка внутрішня функція посилається на локальну змінну, ця змінна буде розміщена в контекстному об'єкті (тобто в купі, а не в стеку). Випадок 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 і код там, а потім перенести код у власні файли. Але це часто неможливо, оскільки змінні, які повинні бути там, недоступні. Простий евал цього не зробить. Я чую нескінченну петлю, можливо.
Рей Фосс

Я насправді не натрапив на цю проблему під час налагодження, тому не шукав способів зняття коду.
Луї-

6
Останній коментар із випуску говорить: Переведення V8 в режим, коли все насильно виділено контекст, можливо, але я не впевнений, як / коли запустити це через інтерфейс Devtools заради налагодження, я іноді хотів би зробити це . Як я можу примусити такий режим?
Сума

2
@ user208769 При закритті як дубліката ми надаємо перевагу тому питання, яке є найбільш корисним для майбутніх читачів. Існує кілька факторів, які допомагають визначити, яке питання є найкориснішим: ваше запитання отримало рівно 0 відповідей, тоді як на цей було отримано декілька відповідей. Тож це питання є найкориснішим із двох. Дати стають визначальним фактором лише за умови, що корисність здебільшого однакова.
Луї

1
Ця відповідь відповідає на власне запитання (Чому?), Але на питання, що мається на увазі - Як отримати доступ до невикористаних змінних контексту для налагодження без додавання додаткових посилань на них у свій код? - краще відповість @OwnageIsMagic нижче.
Зігфрід

30

Як @Louis сказав, що це викликано оптимізаціями v8. Ви можете пройти стек виклику до кадру, де видно цю змінну:

виклик1 виклик2

Або замінити debuggerна

eval('debugger');

eval відірве поточний шматок


1
Майже чудово! Він робить паузу в модулі VM (жовтий) із вмістом debugger, і контекст дійсно доступний. Якщо ви збільшите стек на один рівень до коду, який ви насправді намагаєтеся налагодити, ви повернетесь до того, що не маєте доступу до контексту. Тож це просто трохи незграбно, не в змозі подивитися на код, який ви налагоджуєте, отримуючи доступ до прихованих змінних закриття. Я, проте, я вручу пропозицію, оскільки це позбавляє мене від необхідності додавати код, який, очевидно, не для налагодження, і він дає мені доступ до всього контексту, не деоптимізуючи весь додаток.
Зігфрід

О ... це навіть незграбніше, ніж використовувати жовте evalвікно джерела редагування, щоб отримати доступ до контексту: ви не можете перейти через код (якщо ви не поставите eval('debugger')між усіма рядками, через які хочете перейти.)
Sigfried

Схоже, бувають ситуації, коли певні змінні є невидимими навіть після проходження до відповідного кадру стека; У мене є щось на кшталт controllers.forEach(c => c.update())і потрапив на точку розриву десь глибоко всередині c.update(). Якщо я потім виберу кадр, де controllers.forEach()викликається, controllersне визначено (але все інше в цьому кадрі видно). Я не міг відтворити мінімальну версію, мабуть, може бути якийсь поріг складності, який потрібно пройти чи щось.
PeterT

@PeterT, якщо це <undefined>, ви перебуваєте в неправильному місці, або somewhere deep inside c.update()ваш код переходить в асинхронність, і ви бачите рамку стека async
OwnageIsMagic

6

Я також помітив це у nodejs. Я вважаю (і я визнаю, що це лише здогадка), що коли код компілюється, якщо xвін не з’являється всередині bar, він не робить xдоступним у межах області bar. Це, ймовірно, робить її трохи більш ефективною; проблема полягає в тому, що хтось забув (або не хвилював), що навіть якщо немає xв ньому bar, ви можете вирішити запустити налагоджувач, а отже, все одно потрібно отримати доступ xзсередини bar.


2
Дякую. В основному я хочу вміти пояснити це початківцям javascript краще, ніж "Налагоджувач лежить".
Гейб Коплей

@GabeKopley: Технічно налагоджувач не бреше. Якщо на змінну не посилається, то вона технічно не вкладається. Таким чином, перекладача не потрібно створювати закриття.
slebetman

7
Це не сенс. Під час використання відладчика я часто потрапляв у ситуацію, коли хотів дізнатися значення змінної у зовнішній області, але через це не міг. А на більш філософську ноту, я б сказав, що налагоджувач бреше. Наявність змінної у внутрішній області не повинно залежати від того, використовується вона насправді або є непов'язана evalкоманда. Якщо змінна оголошена, вона повинна бути доступною.
Девід Найпе

2

Ух, справді цікаво!

Як зазначали інші, це, мабуть, пов'язане scope, але конкретніше, пов'язане з цим debugger scope. Коли введений скрипт оцінюється в інструментах розробника, він, схоже, визначає a ScopeChain, що призводить до певної химерності (оскільки він пов'язаний з областю інспектора / налагоджувача). Варіація того, що ви опублікували:

(EDIT - насправді, ви згадуєте це у своєму первісному запитанні, поступається, моє погано! )

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

Сподіваємось, що це та / або вище посилання допомагає. Це мій улюблений вид SO-питань, BTW :)


Дякую! :) Мені цікаво, що ФФ робить інакше. З моєї точки зору як розробника, досвід FF об'єктивно кращий ...
Gabe Kopley

2
"виклик точки перерви в лекс час" я сумніваюся в цьому. Це не те, для чого проривні точки. І я не бачу, чому відсутність інших речей у функції має значення. Сказавши, що якщо це щось схоже на nodejs, то точки прориву можуть бути дуже помилковими.
Девід Найпе
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.