Контекст выполнения илитекущее значение 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, чтобы установить контекст выполнения явно.

Спасибо за чтение!