Наверное многие при изучении javascript сталкиваются с проблемой понимания. Что ж сейчас я попытаюсь вам объяснить механизм работы ключевого слова this.
Для начала давайте рассмотрим алгоритм исполнения ключевого слова this в спецификации.
Итак смотрим на производство PrimaryExpression: this
и видим следующие шаги:
1. Return ? ResolveThisBinding().
И это все. Но было бы не честно не раскрыть механизм этого шага, поэтому продолжим...
Теперь смотрим алгоритм ResolveThisBinding и наблюдаем такие этапы:
1. Let envRec be GetThisEnvironment().
2. Return ? envRec.GetThisBinding().
Первый этап говорит нам о том что нужно получить Environment Record
, при помощи вызова операции GetThisEnvironment.
Второй этап вызывает метод GetThisBinding
у полученного Environment Record
Теперь давайте рассмотрим эти шаги подробнее. Итак открываем для начала алгоритм GetThisEnvironment:
1. Let env be the running execution context's LexicalEnvironment.
2. Repeat,
a. Let exists be env.HasThisBinding().
b. If exists is true, return env.
c. Let outer be env.[[OuterEnv]].
d. Assert: outer is not null.
e. Set env to outer.
Пояснение: В первом шаге достается из Execution Context
содержимое компоненты LexicalEnvironment
и записывается в переменную env
; содержимым является Environment Record
. Второй шаг это бесконечный цикл с условием выхода в теле. Шаг 2.a
сохраняет результат операции HasThisBinding
(смотри примечание под знаком *) в переменную exists
, смысл этой операции проверить Environment Record
на способность устанавливать привязку this
. Шаг 2.b
смотрит на результат предыдущего шага то есть на переменную exists
и выходит из цикла если результат true
. Шаг 2.c
достает значение из поля [[OuterEnv]]
записи Environment Record
и сохраняет в переменную outer
. В шаге 2.d
утверждаем что outer
не может иметь значение null
. Ну и 2.e
это перезапись переменной env
значением переменной outer
.
В конечном итоге когда произойдет шаг 2.b
мы получим результат этой операции GetThisEnvironment. Результат операции - получение Environment Record
, которая поддерживает привязку this
Ну, а теперь осталось рассмотреть операцию/метод Environment Record
- GetThisBinding
. Но есть одна проблема: смотри примечание под знаком *. И поэтому я предлагаю посмотреть на реализацию одного из типов, пусть это будет function Environment Record. Реализация метода GetThisBinding
такая:
1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception.
3. Return envRec.[[ThisValue]].
Внимание, выполнение этого алгоритма недостижимо если функция является стрелочной, так как в предыдущем алгоритме GetThisEnvironment, а именно на этапе выполнения шага 2.a мы получим false; стрелочные функции не имеют способности устанавливать привязку this
Пояснение: Первым шагом утверждаем, что поле [[ThisBindingStatus]]
нашего Environment Record
не имеет значение lexical (абзац выше как раз подтверждает этот шаг). Второй шаг проверяет поле [[ThisBindingStatus]]
на значение uninitialized, если это не так то выбрасывается ошибка (кстати говоря эта ошибка возникает в том случае когда this
не привязан к Environment Record
, которое поддерживает привязку this
; например когда мы делаем наследование класса от другого, но создавая метод "constructor" не привязываем значение this
, то есть не пишем обязательное super(...args)
). Ну и третий шаг это получение значения из поля [[ThisValue]]
, после получения происходит возврат этого значения.
Вот это механизм того как при написании в коде ключевого слова this
вы получаете значение.
И пару слов об [[ThisValue]]
, так как очень непонятно получается, что возникает это поле и в нем уже значение необходимое нам. Итак, это значение устанавливается в 2ух случаях, при вызове функции (шаг 9) и при вызове ключевого слова super(...args)
(шаг 8). За установку отвечает операция BindThisValue, в этом алгоритме происходит установка поля [[ThisValue]]
значением переданным в BindThisValue. Но обращаю ваше внимание, что дополнение про [[ThisValue]]
работает в случае когда у нас function Environment Record, при других типах об объяснении про [[ThisValue]]
- забудьте.
Как-то так, если есть вопросы - буду рад на них ответить.
Примечания:
*
данная операция, а точнее метод имеет разную реализацию так как зависит от того на каком типе Environment Record
применяется. Типы Environment Record
описаны здесь. В зависимости от типа варьируется и итоговый результат.
знакомы ли вы с творчеством demi myrich? у вас прям под капотом весь механизм описан. Очень здорово!