Контекст выполнения илитекущее значение this
— это среда, в которой выполняется функция (или метод).
Рассмотрим этот код:
Выходы:
Ralph Asterix is a mechanical engineer. Ralph likes to grow fruits and vegetables in spare time.
Я вызываю person.printBio()
в строке 12. В соответствии с правилами определения значения this
значение this
для этого вызова устанавливается в объект person
.
Поэтому, когда я ссылаюсь на this.firstName
в строке 7, я фактически получаю доступ к свойству firstName
объекта person
.
Что, если я назначу метод printBio
переменной и вызову его как функцию? Результат разный.
Выходы:
undefined undefined is undefined. undefined likes to undefined in spare time.
О-о! this
совсем не то, что я хотел!
Произошло то, что исходный контекст выполнения был потерян.
Это произошло из-за того, как я вызвал функцию.
Если вы новичок в JavaScript, я призываю вас уделить некоторое время тому, чтобы понять, как определяется значение this
в JavaScript. Наличие прочной ментальной модели того, как работает контекст выполнения, или this
, сделает вас намного лучшим инженером.
Чтобы понять контекст выполнения, важно сначала обсудить глобальный объект.
Глобальный объект
Глобальный объект — это объект, который создается при запуске программы JavaScript. Он всегда существует и всегда доступен в любом месте скрипта. В зависимости от того, где выполняется ваш скрипт, глобальный объект может быть одним из следующих объектов:
Window
, если запустить в браузереglobal
, если запустить в узлеWorkerGlobalScope
, если работает в Worker
WorkerGlobalScope
в этой статье не рассматривается. Не стесняйтесь читать об этом на MDN.
Контекст выполнения
Когда вы вызываете функцию, JavaScript устанавливает контекст выполнения на некоторое значение. Он не будет установлен, только если вы явно установите его самостоятельно.
Какое значение установлено для контекста выполнения, зависит исключительно от того, как вы вызываете функцию или метод.
Итак, как JavaScript определяет, на какое значение должно ссылаться this
?
Всякий раз, когда у вас есть обычный вызов функции, this
устанавливается на глобальный объект. Если этот вызов находится в узле, глобальный объект — это объект с именем global
. Если это происходит в браузере, это объект с именем window
.
Что такое обычный вызов функции?
Это обычный вызов функции, например:
В строках с 3 по 5 я определяю функцию foo. А поскольку это объявление функции, я могу вызвать ее до того, как определю.
Я написал статью на Medium о функциях JavaScript, если вам нужно освежить в памяти, она находится здесь.
Код выше выводит:
Hello [object global]!
Если я запускаю тот же код в браузере, вывод будет таким:
Hello [object Window]!
Что такое нерегулярный вызов функции? Я вернусь к этому через мгновение.
Для вызовов методов значение this
устанавливается равным вызывающему объекту.
Вот пример:
Выходы:
Hello World!
В приведенном выше примере значение this
установлено для вызывающего объекта, то есть foo
.
В результате, когда я обращаюсь к свойству text
в строке 4 (this.text
), я фактически получаю доступ к значению, на которое ссылается foo.text
.
Поскольку свойству text
присвоено значение «Hello World!», console.log(this.text);
выводит «Hello World!».
Ранее я упоминал о «необычных» вызовах функций. Вот что я имел в виду под этим утверждением.
Функции в JavaScript — это первоклассные объекты.
- Их можно присвоить переменным
- Их можно передать другим функциям.
- Они также могут быть возвращены из других функций.
Это свойство функций JavaScript усложняет установку контекста выполнения.
Вот несколько сценариев, в которых теряется исходное выполнение функции/метода.
Присвоение метода переменной и вызов его как обычной функции
Поэтому, когда я вызываю метод, значение this
является вызывающим объектом.
Но что произойдет, если я скопирую метод и назначу его переменной?
Исходный контекст выполнения теряется.
Новая функция имеет глобальный объект в качестве контекста выполнения.
Рассмотрим этот пример:
Выходы:
Hello World!
Пока все отлично…
Давайте скопируем foo.printText
и назначим его переменной printText
и вызовем printText
как обычную функцию.
this
больше не ссылается на объект foo
, а ссылается на глобальный объект.
Когда я пытаюсь получить доступ к свойству text
в глобальном объекте, доступ к этому свойству оценивается как undefined
. Это связано с тем, что этого свойства не существует в глобальном объекте ¯\_(ツ)_/¯.
Исходный контекст выполнения, которым изначально был объект foo
, теперь утерян!
Как решить эту проблему?
Я могу установить контекст выполнения явно.
Например, я могу использовать метод bind
.
Метод bind возвращает новую функцию. Эта функция постоянно привязана к предоставленному контексту выполнения.
Под постоянно связанным я подразумеваю:
- его значение
this
постоянно устанавливается в предоставленный контекст выполнения (некоторое значение).
Первый аргумент, ожидаемый bind
, — это контекст выполнения (значение this
). Любые последующие аргументы для привязки передаются функции/методу, для которого я вызываю bind
.
Вот решение:
Выходы:
Hello World!
Вот что произошло:
foo.printText.bind(foo)
в строке 8 возвращает совершенно новую функцию- Новая функция имеет значение
this
для ссылки на объектfoo
(постоянно!) - Затем он присваивается переменной
printText
printText
теперь ссылается на функцию и может быть вызвана как любая другая функция.- Функция
printText
вызывается в строке 10. - Поскольку я предоставил контекст явно, он выводит
Hello World!
bind
— не единственный способ решить эту конкретную проблему потери контекста выполнения.
Я также мог бы использовать call
или apply
для достижения того же результата.
Существует важное различие между callилиapply и bind.
- bind возвращает совершенно новую функцию, которая постоянно привязана к значению, переданному в качестве аргумента.
- вызов и применение вызова функции с контекстом, который передается в качестве аргумента
Передача функции в качестве аргумента другой функции
Другой сценарий потери исходного контекста функцией/методом — это когда функция передается в качестве аргумента другой функции.
Функции теряют исходный контекст выполнения при передаче в качестве аргументов другим функциям. Вот пример:
Выходы:
undefined
Вот что произошло:
- В строке 12 я вызываю функцию
printMessage
- Я передаю метод
foo.printText
в качестве аргументаprintMessage
- Тело функции
printMessage
состоит из одной строки кода:funcArgument();
— строка 9. - Строка 9 оценивается как
undefined
Этоundefined
, потому что:
-foo.printText
было передано в качестве аргумента
- он потерял исходный контекст выполнения -foo
- новый контекст выполнения установлен на глобальный объект
- глобальный объектне имеет определенного для него свойстваtext
- Таким образом, результатundefined
Опять же, есть несколько способов решить эту проблему. На этот раз я собираюсь использовать метод call
.
Вот что произошло:
- Я добавил второй параметр
context
в функциюprintMessage
(строка 10) context
представляет значениеthis
, которое я хотел бы установить явно равнымfoo.printText
- Строка 11 имеет этот код:
funcArgument.call(context);
- В строке 11 я вызываю
funcArgument
, а также говорю JavaScript установить значениеthis
вcontext
.
Я также могу установить контекст выполнения или this
для ссылки на любой объект, который я хочу. Я могу установить совершенно другой объект, например:
Выходы:
HELLO WORLD!!!!!!!
Вот что произошло:
- Я создал новый объект
bar
— строки 8–10 bar
также имеет свойствоtext
, оно имеет значениеHELLO WORLD!!!!!!!
- В строке 12 я вызываю
printMessage
и передаюbar
в качестве аргументаcontext
- Строка 15 имеет тот же код:
funcArgument.call(context);
- Результат
HELLO WORLD!!!!!!!
Несколько вещей, которые нужно помнить о контексте выполнения в JavaScript
- Контекст выполнения (значение
this
) зависит от того, как вы вызываете функцию или метод. - Это не определяется как определена функция или метод.
- Контекст выполнения — это глобальный объект, когда речь идет об обычных вызовах функций.
- В качестве контекста выполнения задан вызывающий объект для вызова метода.
- Вы можете использовать bind, call, apply, чтобы установить контекст выполнения явно.
Спасибо за чтение!