Эксперты по JavaScript: почему `with` сводит на нет оптимизацию компилятора, связанную с областью видимости

Читая книгу Кайла Симпсона «Вы не знаете JS: Scopes & Closures», он утверждает, что вы должны держаться подальше как от функции eval(), так и от ключевого слова with, потому что всякий раз, когда компилятор видит эти 2 (я перефразирую), он не выполняет некоторые действия. оптимизация, связанная с лексической областью и сохранением местоположения идентификаторов, потому что эти ключевые слова могут потенциально изменить лексическую область, что делает оптимизацию компилятора некорректной (я предполагаю, что оптимизация похожа на компилятор, сохраняющий местоположение каждого идентификатора, чтобы он мог предоставить значение идентификатора без его поиска, когда он запрашивается во время выполнения).

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

Но почему компилятор делает то же самое для ключевого слова with? В книге говорится, что это происходит потому, что with создает новую лексическую область во время выполнения и использует свойства объекта, переданного в качестве аргумента with, для объявления некоторых новых идентификаторов. Я буквально понятия не имею, что это значит, и мне очень трудно визуализировать все это, поскольку все, что связано с компилятором в этой книге, — это сплошная теория.

Я знаю, что могу быть на неправильном пути, в таком случае, пожалуйста, исправьте все мои недоразумения :)


person doubleOrt    schedule 11.09.2017    source источник
comment
@Bergi Я не спрашиваю, почему это вредно, я спрашиваю, почему компилятор не выполняет оптимизацию, связанную с областью действия, если в коде присутствует with. На какой из этих вопросов есть ответ в этой статье?   -  person doubleOrt    schedule 11.09.2017
comment
Причина отказа от использования with в 2017 году не в оптимизации, а в том, что мы пишем код строгого режима или ES6, в котором это даже нелегально.   -  person    schedule 11.09.2017
comment
@torazaburo хорошо, автор утверждает, что, несмотря на все отрицательные моменты, высказанные о with, все они упускают одно из самых важных: with ухудшает производительность. Я просто спрашиваю, почему.   -  person doubleOrt    schedule 11.09.2017


Ответы (2)


Упомянутая здесь оптимизация основана на следующем факте: переменные, объявленные внутри функции, всегда можно определить с помощью простого статического анализа кода (т. е. путем просмотра объявлений var/let и function), а набор объявленных переменных внутри функции функция никогда не меняется.

eval нарушает это предположение, предоставляя возможность изменять локальную привязку (путем введения новых переменных в область действия функции во время выполнения). with нарушает это предположение, вводя новую нелексическую привязку внутри функции, свойства которой вычисляются во время выполнения. Статический анализ кода не всегда может определить свойства объекта with, поэтому анализатор не может определить, какие переменные существуют в блоке with. Важно отметить, что объект, предоставленный with, может изменяться между выполнениями функции, а это означает, что набор переменных в этом лексическом разделе функции никогда не может быть гарантированно непротиворечивым.

Рассмотрим простую функцию:

function foo() {
    var a, b;
    function c() { ... }
    ...
}

Все точки в foo имеют три переменные локальной области видимости: a, b и c. Оптимизатор может прикрепить к функции постоянную «примечание», в котором говорится: «Эта функция имеет три переменные: a, b и c. Это никогда не изменится».

Теперь рассмотрим:

function bar(egg) {
    var a, b;
    function c() { ... }

    with(egg) {
        ...
    }
}

В блоке with неизвестно, какие переменные будут или не будут существовать. Если в with есть a, b или c, мы не знаем до времени выполнения, относится ли это к переменной bar или к переменной, созданной лексической областью видимости with(egg).

Чтобы показать полупрактический пример того, как это проблема, наконец, рассмотрим:

function baz(egg) {
    with(egg) {
        return function() { return whereami; }
    }
}

Когда выполняется внутренняя функция (например, bar({...})()), механизм выполнения просматривает цепочку областей видимости, чтобы найти whereami. Если бы оптимизатору было разрешено прикрепить постоянную примечание к baz, то исполняющая машина немедленно узнала бы, что в замыкании baz функции находится значение whereami, потому что это гарантированно будет домом для whereami (любого аналогичного значения). именованная переменная вверх по цепочке областей действия будет затенена ближайшей). Однако он не знает, существует ли whereami в baz или нет, потому что он может быть условно создан содержимым egg в конкретном запуске bar, который создал эту внутреннюю функцию. Поэтому его приходится проверять, а оптимизацию не использовать.

person apsillers    schedule 11.09.2017
comment
добавление нескольких примеров кода увеличит ценность ответа :) - person doubleOrt; 11.09.2017
comment
мы не знаем этого до времени выполнения - мы даже не всегда знаем, во время выполнения, когда вводится оператор with. Нам нужно проверять объект каждый раз, когда осуществляется доступ к переменной. Также это, конечно, предотвращает оптимизацию компилятора, которая изменяет порядок, дублирует или избегает доступа к переменным. - person Bergi; 11.09.2017
comment
@apsillers ты говоришь о bah, когда говоришь bar? это опечатка? - person doubleOrt; 11.09.2017
comment
@Taurus Да, извините, это опечатка; Я хотел назвать эту функцию bar, но по ошибке оставил ее как foo. Я переименовал вещи в этом примере и отредактировал текст. - person apsillers; 11.09.2017
comment
@ Берги Ой, да; Я даже не думал об этом. Какой кошмар! - person apsillers; 11.09.2017
comment
потому что это может быть условно создано содержимым яйца. Как содержимое egg могло создать переменную внутри baz? Насколько я понимаю, когда вы выполняете поиск LHS переменной внутри оператора with, и она не существует как свойство цели with, Javascript автоматически объявит переменную внутри глобального (если не работает в строгом режиме). Режим). - person doubleOrt; 11.09.2017
comment
@Taurus (полагаю, вы представляете себе baz случай, когда вы выполняете whereami = 5 во внутренней функции :) Тем не менее, прежде чем он сможет решить создать глобальную переменную, движок должен решить, существует ли whereami в текущей цепочке областей видимости или нет. Существует оптимизация, позволяющая сделать это быстро (помечая функции переменными, которые будут существовать внутри их замыканий при запуске), но когда вы используете with, вы теряете эту оптимизацию (поскольку родитель области действия анонимной функции - замыкание, созданное by baz -- может содержать переменные с любыми именами, динамически созданными with). - person apsillers; 11.09.2017
comment
@Taurus Другими словами: строго говоря, родитель области действия закрытия анонимной функции здесь не baz, а теперь существует объект лексической области видимости (переменные которого определены egg) в между анонимной функцией и baz. Этот объект лексической области не может иметь нормальных переменных, которые существуют в цепочке областей видимости? примененные к нему оптимизации. - person apsillers; 11.09.2017
comment
@apsillers Итак, в основном whereami существует в области baz, поэтому я должен сделать примечание о том, что baz - это то место, где вы должны искать whereami, но тогда он также потенциально может существовать в виде динамической области, созданной из egg, так что я думаю, я буду просто не придерживайтесь этого примечания (это должно быть сказано компилятором) ситуации? - person doubleOrt; 11.09.2017
comment
@Taurus Ну, я использую две разные модели (объект лексической области видимости, что на самом деле делает with, по сравнению с изменением существующих областей действия функций, что делает eval), так что я могу немного изменить вас. Строго говоря, whereami не существует в baz, но whereami может существовать в объекте лексической области видимости, который существует до baz в цепочке областей видимости. В отличие от функций, которые могут быть проанализированы статически и не изменяют содержащиеся в них переменные (за исключением раздражающих eval случаев), объект лексической области видимости может изменять содержащиеся в нем переменные в любое время. - person apsillers; 11.09.2017
comment
@Tarus Если бы анонимная функция имела цепочку областей без каких-либо with или eval где-либо в ней, мы могли бы мгновенно узнать, существует ли и где именно whereami в цепочке областей видимости или нет (потому что мы могли бы собрать неизменный набор переменных, которые существуют в области видимости цепочке и никогда не придется снова подниматься по цепочке областей видимости для поиска). - person apsillers; 11.09.2017
comment
@apsillers О, я знаю, что whereami не существует в baz в приведенном вами примере, я просто описывал другой гипотетический пример, где whereami существовал в baz. Кроме того, что вы подразумеваете под объектом лексической области видимости? egg или область действия, созданная with ? - person doubleOrt; 11.09.2017
comment
@Taurus Я понимаю - да, нам нужно сначала проконсультироваться с постоянно меняющимся объектом лексической области видимости, чтобы увидеть, есть ли у него переменная whereami в данный момент, прежде чем мы сможем использовать whereami, которая существует в baz. Под объектом лексической области действия я действительно подразумеваю представление в памяти Лексическое окружение, как определено в спецификации ECMAScript, хотя в этом случае набор переменных в лексическом окружении определяется фактически объектом JavaScript (здесь, egg), так что практической разницы, которую я имел в виду, не так уж и много. - person apsillers; 11.09.2017

Возьмите этот пример:

{
 let a = 1; //stored at 123
 {
   let b = 2; //stored at 124
   console.log(a/*123*/,b/*124*/);
 }
}

А теперь это:

{
 let a = 1;//stored at 123
 with({a:3}){
   console.log(a /*123 ??*/);
 }
}
person Jonas Wilms    schedule 11.09.2017