Введение

Примечание: под “точкой” в тексте подразумевается вызов как метод объекта, т.е. либо 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 элемент, на который вешали обработчик.