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

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

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

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

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

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 блок, не зная, какие переменные будут или не будут существовать. Если есть a, b или же c в with мы не знаем до времени выполнения, если это относится к переменной bar или созданный with(egg) лексическая сфера

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

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

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

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

{
 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 ??*/);
 }
}
Другие вопросы по тегам