JavaScript. Контекст вызова функции
17.06.2021
Теги: JavaScript • Web-разработка • Теория • Функция
В JavaScript this
— это текущий контекст исполнения функции, он определяется в момент вызова. Функцию можно вызвать четырьмя способами и каждый из них определяет свой контекст. Кроме того, режим strict
также влияет на контекст исполнения. Рассмотрим каждый способ вызова функции и посмотрим, на что будет указывать this
в каждом из них.
- Простой вызов функции —
hello('Привет')
- Вызов метода объекта —
user.hello('Привет')
- Вызов с указанием контекста —
hello.call(user, 'Привет')
- Вызов конструктора —
new User('Вася')
1. Простой вызов функции
Здесь все просто — контекстом вызова будет глобальный объект window
или undefined
, в зависимости от режима strict
.
function foo() { console.log(this); // window bar(); function bar() { console.log(this); // window } } foo();
'use strict'; function foo() { console.log(this); // undefined bar(); function bar() { console.log(this); // undefined } } foo();
2. Вызов метода объекта
Метод — это функция, хранящаяся в объекте. При вызове метода this
— это объект, которому принадлежит метод.
var user = { name: 'Вася', hello: function () { console.log('Привет,', this.name); } }; user.hello(); // Привет, Вася
Ловушка — потеря контекста
Метод объекта можно переместить в отдельную переменную. При вызове метода с использованием этой переменной можно подумать, что this
— это объект, в котором определён метод. На самом деле, если метод вызван без объекта, происходит простой вызов функции, и this
становится глобальным объектом window
или undefined
.
var user = { surname: 'Иванов', show: function () { console.log('Фамилия', this.surname); } }; setTimeout(user.show, 1000); // Фамилия undefined
Фактически, последняя строка может быть переписана как:
var f = user.show; setTimeout(f, 1000); // контекст user потерян
Метод setTimeout()
в браузере имеет особенность — он устанавливает this=window
для вызова функции. Таким образом, для this.surname
он пытается получить window.surname
, которого не существует. В других подобных случаях this
обычно просто становится undefined
.
Самый простой способ обойти ловушку — это обернуть вызов в анонимную функцию, создав замыкание:
var user = { surname: 'Иванов', show: function () { console.log('Фамилия', this.surname); } }; setTimeout(function () { user.show(); }, 1000); // Фамилия Иванов
Теперь объект user
достаётся из замыкания, а затем вызывается его метод show()
.
3. Вызов с указанием контекста
Методы call()
и apply()
объекта Function
позволяют явно указать this
.
func.call(context, arg1, arg2, ...);
При этом вызывается функция func
, первый аргумент call
становится её this
, а остальные передаются «как есть».
func.apply(context, [arg1, arg2]);
Вызов функции при помощи func.apply
работает аналогично func.call
, но принимает массив аргументов вместо списка.
function showFullName() { console.log(this.firstName + ' ' + this.lastName); } var user = { firstName: 'Василий', lastName: 'Иванов' }; // функция вызовется с this=user showFullName.call(user) // Василий Иванов
4. Вызов конструктора
Функции-конструкторы являются обычными функциями. Но есть два соглашения:
- Имя функции-конструктора должно начинаться с большой буквы
- Функция-конструктор должна вызываться при помощи оператора
new
function User(name) { this.name = name; this.isAdmin = false; } let user = new User('Вася'); alert(user.name); // Вася alert(user.isAdmin); // false
Когда функция вызывается как new User(…)
, происходит следующее:
- Создаётся новый пустой объект, и он присваивается
this
- Выполняется код функции. Обычно он модифицирует
this
, добавляя туда новые свойства - Возвращается значение
this
Другими словами, вызов new User(…)
делает примерно вот что:
function User(name) { // this = {}; (неявно) // добавляет свойства к this this.name = name; this.isAdmin = false; // return this; (неявно) }
Вместо заключения
Мы уже видели пример потери this
. Как только метод передаётся отдельно от объекта — this
теряется.
var user = { surname: 'Иванов', show: function () { console.log('Фамилия', this.surname); } }; setTimeout(user.show, 1000); // Фамилия undefined
В современном JavaScript у функций есть встроенный метод bind
, который позволяет зафиксировать this
.
var user = { surname: 'Иванов', show: function () { console.log('Фамилия', this.surname); } }; user.show.bind(user) setTimeout(user.show, 1000); // Фамилия Иванов
Поиск: JavaScript • Web-разработка • Теория • Функция • this • bind • call • apply