this
Поведение ключевого слова this
в JavaScript несколько отличается по сравнению с остальными языками. Имеются также различия при использовании this
в строгом и нестрогом режиме.
В большинстве случаев значение this
определяется тем, каким образом вызвана функция. Значение this
не может быть установлено путём присваивания во время исполнения кода и может иметь разное значение при каждом вызове функции. В ES5 представлен метод bind()
, который используется для привязки значения ключевого слова this независимо от того, как вызвана функция
. Также в ES2015 представлены стрелочные функции
, которые не создают собственные привязки к this
(они сохраняют значение this
лексического окружения, в котором были созданы).
Интерактивный пример
Синтаксис
this
Значение
Свойство контекста выполнения кода (global, function или eval), которое в нестрогом режиме всегда является ссылкой на объект, а в строгом режиме может иметь любое значение.
Global контекст
В глобальном контексте выполнения (за пределами каких-либо функций) this
ссылается на глобальный объект вне зависимости от режима (строгий или нестрогий).
// В браузерах, объект window также является объектом global:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b); // "MDN"
console.log(b); // "MDN"
Примечание: Вы всегда можете легко получить объект global, используя глобальное свойство globalThis
, независимо от текущего контекста, в котором выполняется ваш код.
Function контекст
В пределах функции значение this
зависит от того, каким образом вызвана функция.
Простой вызов
Поскольку следующий код не в строгом режиме
, и значение this
не устанавливается вызовом, по умолчанию будет использоваться объект global, которым в браузере является
.window
function f1() {
return this;
}
// В браузере:
f1() === window; // window - глобальный объект в браузере
// В Node:
f1() === global; // global - глобальный объект в Node
В строгом режиме, если значение this
не установлено в контексте выполнения, оно остаётся undefined
, как показано в следующем примере:
function f2() {
"use strict"; // см. strict mode
return this;
}
f2() === undefined; // true
Примечание: Во втором примере this
должно иметь значение
, потому что функция undefined
f2
была вызвана напрямую, а не как метод или свойство объекта (например, window.f2()
). Реализация этой особенности не поддерживалась в некоторых браузерах, когда они впервые начали поддерживать строгий режим
. В результате они некорректно возвращали объект window
.
Для того, чтобы при вызове функции установить this
в определённое значение, используйте call()
или apply()
, как в следующих примерах.
Пример 1
// В качестве первого аргумента методов call или apply может быть передан объект,
// на который будет указывать this.
var obj = { a: "Custom" };
// Это свойство принадлежит глобальному объекту
var a = "Global";
function whatsThis() {
return this.a; //значение this зависит от контекста вызова функции
}
whatsThis(); // 'Global'
whatsThis.call(obj); // 'Custom'
whatsThis.apply(obj); // 'Custom'
Пример 2
function add(c, d) {
return this.a + this.b + c + d;
}
var o = { a: 1, b: 3 };
// Первый параметр - это объект для использования в качестве
// 'this', последующие параметры передаются как
// аргументы функции call
add.call(o, 5, 7); // 16
// Первый параметр - это объект для использования в качестве
// 'this', второй - массив, чьи члены используются
// в качестве аргументов функции call
add.apply(o, [10, 20]); // 34
Обратите внимание, что в нестрогом режиме, если значение, переданное в call
или apply как
this
, не является объектом, будет сделана попытка преобразовать его в объект с помощью внутренней операции ToObject
. Таким образом, если переданное значение является примитивом, таким как 7
или 'foo'
, оно будет преобразовано в Object
с использованием связанного конструктора, так что примитивное число 7
будет преобразовано в объект так, как будто с помощью new Number(7)
, а строка 'foo'
- как будто с помощью new String('foo')
, например
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call("foo"); // [object String]
Метод bind()
Вызов f.bind(someObject)
создаёт новую функцию с таким же телом и окружением, что и у f
, но значение this
указывает на первый аргумент bind
, независимо от того, как вызывается функция.
function f() {
return this.a;
}
const g = f.bind({ a: "qwerty" });
console.log(g()); // qwerty
const h = g.bind({ a: "yoo" }); // bind сработает только один раз!
console.log(h()); // qwerty
const o = { a: 37, f, g, h };
console.log(o.a, o.f(), o.g(), o.h()); // 37 37 qwerty qwerty
this
в стрелочных функциях
Стрелочные функции создают замыкания для значения this
из окружающего контекста выполнения. В следующем примере мы создаём объект obj
с методом getThisGetter
, который возвращает функцию, которая возвращает значение this
. Возвращаемая функция является стрелочной, поэтому её this
связано с this
окружающей функции. Значение this
внутри getThisGetter
может быть установлено при вызове, который, в свою очередь, устанавливает возвращаемое значение возвращаемой функции. Мы будем считать, что getThisGetter
является нестрогой функцией, то есть она находится внутри нестрогого скрипта и не вложена в класс или строгую функцию.
const obj = {
getThisGetter() {
const getter = () => this;
return getter;
},
};
Если вызвать getThisGetter
как метод объекта obj
, то это свяжет this
с obj
внутри его тела. Возвращаемая функция присвоена переменной fn
. Теперь при вызове fn
возвращаемое значение this
по-прежнему задаётся вызовом getThisGetter
, то есть obj
. Если бы возвращаемая функция не была стрелочной, то при таких вызовах значение this
было бы globalThis
, поскольку getThisGetter
не является строгой.
const fn = obj.getThisGetter();
console.log(fn() === obj); // true
Но будьте осторожны при отвязывании метода obj
без его вызова, потому что getThisGetter
всё ещё метод, который имеет изменяющееся значение this
. Вызов fn2()()
в следующем примере возвращает globalThis
, потому что он следует за this
из fn2()
, который является globalThis
, поскольку вызывается без привязки к какому-либо объекту.
const fn2 = obj.getThisGetter;
console.log(fn2()() === globalThis); // true в нестрогом режиме
Такое поведение очень полезно при определении обратных вызовов. Обычно каждое функциональное выражение создаёт свою собственную привязку this
, которая перекрывает значение this
окружающей области видимости. Если вам не важно значение this
, вы можете определять функции как стрелочные и создавать привязки this
только там, где это необходимо (например, в методах класса). Смотрите пример с setTimeout()
.
В методе объекта
Когда функция вызывается как метод объекта, используемое в этой функции ключевое слово this
принимает значение объекта, по отношению к которому вызван метод.
В следующем примере, когда вызвано свойство o.f()
, внутри функции this
привязано к объекту o.
var o = {
prop: 37,
f: function () {
return this.prop;
},
};
console.log(o.f()); // logs 37
Необходимо отметить, что на поведение this
совсем не влияет то, как или где была определена функция. В предыдущем примере мы определили функцию внутри свойства f
во время определения объекта o
. Однако, мы могли бы также просто определить сначала функцию, а затем закрепить её за свойством o.f
. В этом случае поведение this
не изменится:
var o = { prop: 37 };
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
Эти примеры показывают, что имеет значение только то, что функция была вызвана из свойства f
объекта o
.
Аналогично, привязывание this
обуславливается наличием ближайшей ссылки на объект или свойство. В следующем примере, когда мы вызываем функцию, мы обращаемся к ней как к методу g
объекта o.b
. На этот раз во время выполнения, this
, что находится внутри функции, будет ссылаться на o.b
. Тот факт, что объект является членом объекта o
, не имеет значения; важна только ближайшая ссылка.
o.b = { g: independent, prop: 42 };
console.log(o.b.g()); // logs 42
this
в цепочке object's prototype
Это же представление справедливо и для методов, определённых где-либо в цепочке object's prototype. Если метод находится в цепочке прототипов, то this
ссылается на объект, на котором был вызван метод, т.е. так, словно метод является методом самого объекта, а не прототипа.
var o = {
f: function () {
return this.a + this.b;
},
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
В этом примере объект, который присвоен переменной p
, не имеет собственного свойства f
, а наследует это свойство от своего прототипа. Однако, совершенно неважно, что поиск свойства f в конце концов обнаружит его на объекте o
. Поскольку поиск начался с p.f
, то и свойство this
внутри функции f
будет ссылаться на объект p
. Таким образом, если f
вызывается как метод p
, то и this
относится к p
. Это полезная особенность прототипного наследования JS.
this
с геттерами/сеттерами
Все те же утверждения справедливы, если функция вызывается из геттера или сеттера. Для функции, которая используется как геттер или сеттер this
привязан к объекту, свойство которого необходимо извлечь через геттер/сеттер.
function modulus() {
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
re: 1,
im: -1,
get phase() {
return Math.atan2(this.im, this.re);
},
};
Object.defineProperty(o, "modulus", {
get: modulus,
enumerable: true,
configurable: true,
});
console.log(o.phase, o.modulus); // logs -0.78 1.4142
В конструкторе
Когда функция используется как конструктор (с ключевым словом new
), this
связано с создаваемым новым объектом.
Примечание: по умолчанию конструктор возвращает объект, на который ссылается this
, но он может вернуть и другой объект (если возвращаемое значение не является объектом, тогда будет возвращён объект с this
).
/*
* Конструктор работает таким образом:
*
* function MyConstructor(){
* // фактический код, составляющий тело функции.
* // создание свойств с |this| по
* // желанию, определяя их значения; например,
* this.fum = "nom";
* // и т.д.
*
* // Если функция возвращает выражение,
* // возвращающее объект, этот объект будет
* // результатом выражения |new|. В обратном случае,
* // результат выражения - объект,
* // в данный момент привязанный к |this|
* // (т.е. наиболее часто встречающийся случай).
* }
*/
function C() {
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2() {
this.a = 37;
return { a: 38 };
}
o = new C2();
console.log(o.a); // logs 38
В последнем примере (C2
), из-за того, что конструктор вернул объект, новый объект, к которому было привязано this
, был просто отброшен. (Это фактически делает выражение "this.a = 37;
" "мёртвым" кодом. Он не является буквально нерабочим, так как он выполняется, но он может быть изъят без каких-либо внешних эффектов.)
call
и apply
Когда в теле функции используется ключевое слово this
, его значение может быть привязано к конкретному объекту в вызове при помощи методов call
или apply
, которые наследуются всеми функциями от Function.prototype
.
function add(c, d) {
return this.a + this.b + c + d;
}
var o = { a: 1, b: 3 };
// Первый параметр - это объект, который следует использовать как
// 'this', последующие параметры передаются
// как аргументы при вызове функции
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// Первый параметр - объект, который следует использовать как
// 'this', второй параметр - массив,
// элементы которого используются как аргументы при вызове функции
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
Необходимо отметить, что если методам call
и apply
передаётся значение с this
, которое не является при этом объектом, будет предпринята попытка конвертировать значение в объект, используя внутреннюю операцию ToObject
. Если переданное значение является примитивным типом, например 7
или 'foo'
, оно будет преобразовано в объект с использованием родственного конструктора, так примитив 7
преобразовывается в объект через new Number(7),
а строка 'foo'
в объект через new String('foo'),
и т.д.
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
Как обработчик событий DOM
Когда функция используется как обработчик событий, this
присваивается элементу с которого начинается событие (некоторые браузеры не следуют этому соглашению для обработчиков, добавленных динамически с помощью всех методов, кроме addEventListener
).
// Когда вызывается как обработчик, связанный элемент становится синим
function bluify(e) {
// Всегда true
console.log(this === e.currentTarget);
// true, когда currentTarget и target один объект
console.log(this === e.target);
this.style.backgroundColor = "#A5D9F3";
}
// Получить список каждого элемента в документе
var elements = document.getElementsByTagName("*");
// Добавить bluify как обработчика кликов, чтобы при
// нажатии на элемент он становился синим
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener("click", bluify, false);
}
В инлайновом обработчике событий
Когда код вызван из инлайнового обработчика, this
указывает на DOM-элемент, в котором расположен код события:
<button onclick="alert(this.tagName.toLowerCase());">Показать this</button>
Код выше выведет 'button
'. Следует отметить, что this
будет указывать на DOM-элемент только во внешних (не вложенных) функциях:
<button onclick="alert((function() {return this;} ()));">
Показать вложенный this
</button>
В этом случае this
вложенной функции не будет установлен, так что будет возвращён global/window объект.
Спецификации
Specification |
---|
ECMAScript Language Specification # sec-this-keyword |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
- Строгий режим
- All this, статья о
this
в разном контексте - Краткое объяснение ключевого слова 'this' в JavaScript