Урок 05

this

this — ссылка на контекст выполнения. Значение зависит от того, как функция вызвана, а не где объявлена.

Правила

1. Метод объекта

this = объект перед точкой:

const user = {
    name: 'Алиса',
    greet() {
        return this.name;
    },
};

user.greet(); // 'Алиса'

2. Обычная функция

this = undefined в strict mode, глобальный объект в нестрогом:

function test() {
    'use strict';
    console.log(this); // undefined
}

test();

3. Стрелочная функция

Своего this нет. Берётся из внешней области:

const user = {
    name: 'Алиса',
    greet: () => {
        return this.name; // this не объект
    },
};

user.greet(); // undefined

Стрелочные не подходят для методов, где нужен this объекта.

4. Конструктор

this = новый объект:

function User(name) {
    this.name = name;
}

const user = new User('Алиса');
user.name; // 'Алиса'

5. call, apply, bind

Явное указание this:

function greet() {
    return `Привет, ${this.name}`;
}

const user = { name: 'Алиса' };

greet.call(user);   // 'Привет, Алиса'
greet.apply(user);  // то же
const bound = greet.bind(user);
bound();            // то же

Потеря this

const user = {
    name: 'Алиса',
    greet() {
        return this.name;
    },
};

const fn = user.greet;
fn(); // undefined, this потерян

Решения:

// bind
const fn = user.greet.bind(user);

// стрелочная обёртка
const fn = () => user.greet();

call vs apply vs bind

  • call — вызывает с аргументами через запятую
  • apply — вызывает с массивом
  • bind — возвращает новую функцию
fn.call(ctx, 1, 2);
fn.apply(ctx, [1, 2]);
const bound = fn.bind(ctx, 1);

Стрелочные методы в классах

class User {
    name = 'Алиса';

    greet = () => {
        return this.name;
    };
}

const user = new User();
const fn = user.greet;
fn(); // 'Алиса' — this сохранён

Стрелочное поле захватывает this из класса.

Метод в setTimeout

const user = {
    name: 'Алиса',
    greet() {
        setTimeout(function() {
            console.log(this.name); // undefined
        }, 1000);
    },
};

Решения:

// стрелочная
greet() {
    setTimeout(() => {
        console.log(this.name); // 'Алиса'
    }, 1000);
}

// или bind
greet() {
    setTimeout(function() {
        console.log(this.name);
    }.bind(this), 1000);
}

В обработчиках DOM

this в обычной функции = элемент:

button.addEventListener('click', function() {
    console.log(this); // button
});

В стрелочной — из внешней области:

button.addEventListener('click', () => {
    console.log(this); // не button
});

Современный код чаще использует event.currentTarget:

button.addEventListener('click', (e) => {
    console.log(e.currentTarget); // button
});

Порядок приоритетов

От высшего к низшему:

  1. new — this = новый объект
  2. bind, call, apply — явное указание
  3. Метод объекта — this = объект
  4. Обычный вызов — undefined (strict) или глобальный объект
  5. Стрелочная — this из внешней области

Итоги

  • this зависит от способа вызова
  • Метод — объект перед точкой
  • Стрелочные — this из внешней области
  • call, apply, bind для явного указания
  • new для конструктора
  • Потеря this — частая ошибка