Введение
Примечание: под “точкой” в тексте подразумевается вызов как метод объекта, т.е. либо obj.fn()
, либо obj['fn']()
Глобальным контекстом функций является Window при отсутствии “use strict” и undefined при его наличии.
1. this в классах и функциях-конструкторах
Является экземпляром класса, который вы создаете с помощью new, и устанавливается в момент создания этого экземпляра
class Test {
constructor(name) {
// this = объекту, который мы создаем после объявления класса.
// Устанавливается в момент вызова new
this.name = name;
}
}
const test = new Test('Имя, которое установится в поле name созданного объекта')
// Объект, который записался в test, во время создания
// экземпляра был в this класса Test, поэтому мы смогли установить его поле name
2. this в обычных функциях
Контекст обычной функции устанавливается в момент вызова функции. Это называется “поздним связыванием”.
Если при вызове слева от названия функции нет точки, то контекстом функции является глобальный контекст (undefined в strict моде и window без него). Если же при вызове функции слева от её названия есть точка, то контекстом функции будет являться объект, находящийся слева от точки.
obj /* <-- контекст функции*/
.fn();
fn(); // контекст равен глобальному контексту (undefined в strict моде и window без него)
[1] Если метод присвоить в переменную, а затем функцию вызвать из этой переменной, контекст будет потерян, потому что слева от точки при вызове ничего не будет
const obj = {
fn() {
console.log(this)
},
};
obj.fn(); // obj
const fn = obj.fn;
fn() // глобальный контекст (undefined в strict моде и window без него)
[2] Если обычную функцию, при вызове которой контекст был равен глобальному контексту, присвоить в объект, а затем вызвать эту функцию как метод объекта, контекстом функции станет этот объект, потому что слева от точки при вызове функции будет расположен этот объект
function fn() {
console.log(this);
}
fn(); // глобальный контекст (undefined в strict моде и window без него)
const obj = {};
obj.fn = fn;
obj.fn(); // obj
[3] Если передать ссылку на метод в функцию и вызвать его внутри неё, то он будет вызван как обычная функция, а не как метод объекта, поэтому его контекст будет утерян.
const someFn = (callback) => {
callback(); // любая функция/метод, переданная сюда, будет вызвана без точки слева,
// т.е. не от имени объекта. Это означает, что её контекст будет утерян
};
const obj = {
method: function() {
console.log(this);
},
};
someFn(obj.method) // this будет равен undefined при use strict и window без него
Тем не менее мы можем избежать такого поведения, обернув вызов нашего метода в обычную или стрелочную функцию:
const someFn = (callback) => {
callback();
/*
Контекст все равно будет утерян, но утерян он будет у переданной функции,
т.е. у нашей функции-обертки
Вызов же метода в этой функции-обертке все равно делается через точку,
т.е. от имени объекта.
Благодаря этому, мы избегаем потери контекста
*/
};
const obj = {
method: function() {
console.log(this);
},
};
someFn(() => obj.method()); // this в методе будет равен obj
someFn(function() { obj.method() }); // this в методе будет равен obj
3. this в стрелочных функциях
Контекст стрелочной функции устанавливается в момент объявления функции. Это называется “ранним связыванием”. Это также означает то, что bind/apply/call не дадут никакого результата при вызове на стрелочной функции.
const fn = () => console.log(this); // this должен быть глобальным контекстом
// (undefined в strict моде и window без него)
fn.call({
name: 'новый контекст',
}); /* Вызов через call должен принудительно привязать
новый контекст к функции, но тем не менее this все равно равен глобальному контексту
(undefined в strict моде и window без него) */
Стрелочная функция не создает собственный контекст, она берет его из внешнего лексического окружения:
[1] Если вы объявляете стрелочную функцию в глобальном контексте (не внутри функций/методов, но при этом сама стрелочная функция может быть методом), то контекстом этой стрелочной функции является глобальный контекст (undefined в strict моде и window без него)
const fn = () => console.log(this);
const obj = {
fn: () => console.log(this) /* контекст создают только функции,
поэтому даже если стрелочная функция сама является
методом объекта (не обернутого в функцию),
контекстом для этой функции будет глобальный контекст
(undefined в strict моде и window без него) */,
};
fn(); // глобальный контекст (undefined в strict моде и window без него)
obj.fn(); // глобальный контекст (undefined в strict моде и window без него)
[2] Если вы объявляете стрелочную функцию внутри другой функции/метода, то контекстом этой стрелочной функции будет контекст функции/метода, в котором эта стрелочная функция объявлена.
const obj = {
fn() {
/* <-- стрелочная функция возьмет контекст этой функции,
// а он равен obj, если вызывать ее через obj */
const arrowFn = () => console.log(this);
arrowFn();
},
};
obj.fn(); // obj
[3] Исходя из пункта [1] и [2] можно сделать следующий совмещенный пример, чтобы глубже понять, как это работает:
const obj = {
// <-- контекст метода fn будет равен obj1,
// если вызвать этот метод через точку (obj1.fn())
fn1() {
// <-- контекст стрелочной функции будет определен этой функцией,
// т.к. она является ближайшей для него функцией
return {
fn2: () => {
// функция возьмет контекст ближайшей внешней функции
console.log(this);
},
};
},
};
obj.fn1().fn2(); // this стрелочной функции = obj. fn1 возвращает внутренний объект,
// на котором вызывается метод fn2. Метод fn2 берет контекст ближайшей к нему функции -
// fn1, а та, в свою очередь, вызвана как метод объекта,
// значит ее контекстом и контекстом стрелочной функции будет obj
4. this в обработчиках событий addEventListener
Это редкий случай, но учтите, что this будет ссылаться на DOM элемент, на который вешали обработчик.