2015-02-07 2 views
99

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

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 по-прежнему не определена.

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

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

+2

может быть, он привыкает не- переменные из вашего пути для вас ... – dandavis

+0

markle976, кажется, говорит, что строка 'debugger;' фактически не называется m внутри 'bar'. Итак, посмотрите на трассировку стека, когда она приостанавливается в отладчике: включена ли функция 'bar' в stacktrace? Если я прав, то стоп-трасса должна сказать, что она приостановлена ​​в строке 5, в строке 7, в строке 9. –

+0

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

ответ

98

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

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

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

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

Единственное решение, которое я мог думать о том, что всякий раз, когда DevTools это, мы бы deopt весь код и перекомпилировать с принудительным распределением контекста. Тем не менее, это привело бы к резкому повышению производительности при помощи 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(); 
+6

Вы поняли способ удаления кода? Мне нравится использовать отладчик как REPL и код там, а затем передать код в свои собственные файлы. Но часто это невозможно, поскольку переменные, которые должны быть там, недоступны. Простой eval этого не сделает. Я слышу бесконечный цикл. –

+0

Я не сталкивался с этой проблемой во время отладки, поэтому я не искал способы деактивировать код. – Louis

+4

Последний комментарий к проблеме: * Включение V8 в режим, где все принудительно выделено контекстом, возможно, но я не уверен, как/когда запускать это через DevTools UI. * Для отладки я бы иногда хотел для этого. Как я могу заставить такой режим? – Suma

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 :)

+0

Спасибо! :) Мне интересно, что делает FF по-другому. С моей точки зрения, как разработчик, опыт FF объективно лучше ... –

+2

«вызов точки прерывания в lex time» Я сомневаюсь. Это не то, что нужно для контрольных точек. И я не понимаю, почему имеет значение отсутствие других функций в функции. Сказав это, если это что-то вроде nodejs, то точки останова могут быть очень ошибочными. –

6

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

+2

Спасибо. В принципе, я хочу, чтобы это объяснить начинающим javascript лучше, чем «Отладчик лжет». –

+0

@GabeKopley: Технически отладчик не лжет. Если переменная не указана, то она не заключена в техническом отношении. Таким образом, интерпретатору не нужно создавать закрытие. – slebetman

+2

Дело не в этом. При использовании отладчика я часто бывал в ситуации, когда мне хотелось знать значение переменной во внешней области, но не могло из-за этого. И на более философской ноте я бы сказал, что отладчик лжет. Независимо от того, существует ли переменная во внутренней области, она не должна зависеть от того, действительно ли она используется или существует ли несвязанная команда eval. Если переменная объявлена, она должна быть доступна. –

2

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

Как уже упоминалось, это похоже на scope, но более конкретно, относящийся к debugger scope. Когда внедренный скрипт оценивается в инструментах разработчика, он, как представляется, определяет 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

+0

Спасибо за ссылки на источник! –

9

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

call1 call2

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

eval('debugger'); 

eval будет deopt текущий кусок

+0

Почти здорово! Он приостанавливается в модуле VM (желтый) с содержимым 'debugger', и контекст действительно доступен. Если вы повышаете стек на один уровень до кода, который вы пытаетесь отлаживать, вы снова не имеете доступа к контексту. Так что это просто немного неуклюже, не имея возможности смотреть на код, который вы отлаживаете, при доступе к скрытым переменным закрытия. Тем не менее, я буду поддерживать, поскольку это избавляет меня от необходимости добавлять код, который явно не предназначен для отладки, и он дает мне доступ ко всему контексту без деоптимизации всего приложения. – Sigfried

+0

Ох ... это даже clunkier, чем использовать желтое окно eval'ed source, чтобы получить доступ к контексту: вы не можете пройти через код (если вы не поместите 'eval ('debugger')' между всеми строками, которые вы хотите пройти.) – Sigfried

Смежные вопросы