Слово this: управление контекстом выполнения в JavaScript

fbb0872fcb8afe298ba0714842f75ce4.png

Привет, Хабр! Сегодня мы поговорим о this, потому что без четкого понимания, как работает this, ваш код может стать источником путаницы и ошибок.

this в JS — это ключевое слово, которое ссылается на текущий контекст выполнения. Его значение зависит от того, где и как была вызвана функция, а не от того, где она была определена.

В этой статье мы разберем все способы работы с контекстом выполнения, чтобы вы могли уверенно использовать this в любом сценарии.

Разные контексты this

Глобальный контекст: поведение this вне функций

Когда вы находитесь на верхнем уровне кода — без функций и объектов — this ссылается на глобальный объект. В браузерах это window, а в Node.js — global.

console.log(this); // В браузере выведет объект window

Здесь this ссылается на глобальный объект. Если вы используете this вне любого контекста, вы всегда получите глобальный объект.

Но как это повлияет на функции? Рассмотрим следующий пример:

function showContext() {
    console.log(this);
}

showContext(); // Вызовет функцию, выведет window (или global в Node.js)

Согласитесь, такое поведение может удивить, особенно если вы ожидали, что this будет ссылаться на что-то другое. Если вы хотите, чтобы this указывал на конкретный объект в глобальном контексте, нужно обернуть его в другой объект:

const myObject = {
    name: 'My Object',
    showContext: showContext,
};

myObject.showContext(); // выведет myObject

Объектный контекст: как this ссылается на текущий объект в методах

Когда функция вызывается как метод объекта, this указывает на объект, к которому принадлежит метод. Это, возможно, одна из самых удобных особенностей this.

const user = {
    name: 'Polina',
    greet() {
        console.log(`Hello, my name is ${this.name}`);
    },
};

user.greet(); // Hello, my name is Polina

В этом примере, когда greet() вызывается, this ссылается на объект user, и мы получаем доступ к его свойствам. Но что происходит, если мы передадим метод в другую функцию?

const greetFunc = user.greet;
greetFunc(); // Hello, my name is undefined

Здесь this больше не указывает на user, потому что метод был вызван вне контекста объекта.

Функциональный контекст: отличия при вызове функций как методов и обычных функций

Когда функция вызывается просто как функция (не как метод объекта), this снова указывает на глобальный объект (в строгом режиме это будет undefined).

function showThis() {
    console.log(this);
}

showThis(); // window (или undefined в strict mode)

Но если вы вызываете функцию как метод объекта, this будет ссылаться на объект:

const obj = {
    value: 42,
    showValue() {
        console.log(this.value);
    },
};

obj.showValue(); // 42

Стрелочные функции: объяснение, почему this не изменяется в стрелочных функциях

Стрелочные функции сохраняют this из окружающего контекста. То есть this в стрелочной функции будет тем же самым, что и this в родительской функции, где она была объявлена:

const person = {
    name: 'Ivan',
    greet: function () {
        const arrowFunc = () => {
            console.log(`Hi, I'm ${this.name}`);
        };
        arrowFunc();
    },
};

person.greet(); // Hi, I'm Ivan

Здесь стрелочная функция arrowFunc сохраняет контекст this, который указывает на объект person.

Однако будьте осторожны: если вы попробуете использовать стрелочную функцию в методах объектов, это приведет к тому, что this не будет ссылаться на объект:

const obj = {
    name: 'Artem',
    show: () => {
        console.log(this.name);
    },
};

obj.show(); // undefined

Здесь this ссылается на глобальный объект (или undefined в строгом режиме), и вы не получите ожидаемого результата.

Изменение контекста выполнения

Теперь посмотрим, как управлять контекстом выполнения с помощью методов call, apply и bind. Эти инструменты позволяют настраивать, на что ссылается this, в зависимости от вашего желания.

Методы call, apply и bind: как и когда их использовать для управления контекстом

Метод call позволяет вызывать функцию с указанным значением this и аргументами, переданными отдельно.

func.call(thisArg, arg1, arg2, ...);
const person = {
    name: 'Artem',
};

function greet(greeting) {
    console.log(`${greeting}, my name is ${this.name}`);
}

greet.call(person, 'Hello'); // Hello, my name is Artem

Используем call, чтобы указать, что this в функции greet ссылается на объект person. Мы также передаем аргумент 'Hello', который функция использует.

Метод apply работает аналогично call, но принимает аргументы в виде массива.

func.apply(thisArg, [argsArray]);
const person = {
    name: 'Nikita',
};

function greet(greeting, punctuation) {
    console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}

greet.apply(person, ['Hi', '!']); // Hi, my name is Nikita!

Метод bind создает новую функцию, которая при вызове будет иметь заданное значение this, а также может принимать предварительно определенные аргументы. В отличие от call и apply, bind не вызывает функцию немедленно, а возвращает новую функцию.

const boundFunction = func.bind(thisArg, arg1, arg2, ...);
const person = {
    name: 'Charlie',
};

function greet(greeting) {
    console.log(`${greeting}, my name is ${this.name}`);
}

const greetCharlie = greet.bind(person);
greetCharlie('Hey'); // Hey, my name is Charlie

Здесь создаем новую функцию greetCharlie, которая всегда будет ссылаться на объект person.

Классы и контекст: как работать с this в классах

Когда мы работаем с классами в JavaScript, важно помнить, что this в методах классов указывает на экземпляр класса. Однако, если вы передаете метод класса как коллбэк, this может потеряться.

Пример:

const person = {
    name: 'Dave',
};

function introduce(greeting, age) {
    console.log(`${greeting}, I am ${this.name} and I am ${age} years old.`);
}

introduce.call(person, 'Hello', 30); // Hello, I am Dave and I am 30 years old.
introduce.apply(person, ['Hi', 25]); // Hi, I am Dave and I am 25 years old.

Здесь используем bind в конструкторе, чтобы убедиться, что this всегда ссылается на экземпляр Person, даже если метод вызывается вне контекста класса. Если бы мы не использовали bind, мы бы получили undefined в this.name при вызове greet через setTimeout.

Прочие фичи

Строгий режим: предотвращение неожиданных значений this

Строгий режим — это способ задать правила, которые помогают избежать ошибок, упрощают диагностику и повышают безопасность кода. В строгом режиме, если функция вызывается без контекста (как обычная функция), this будет undefined, а не ссылаться на глобальный объект.

function showThis() {
    console.log(this);
}

showThis(); // В браузере выведет window

Пример со строгим режимом:

'use strict';

function showThis() {
    console.log(this);
}

showThis(); // undefined

Методы объектов и this

Когда вы определяете методы в объектах, важно помнить, что this внутри метода всегда будет ссылаться на сам объект, даже если метод передается в другую функцию или контекст. Однако, если вы не используете bind, вы можете столкнуться с неожиданными результатами.

Пример:

const obj = {
    name: 'Eva',
    showName() {
        console.log(this.name);
    },
};

const show = obj.showName;
show(); // undefined (в строгом режиме) или 'Eva' (в обычном)

Здесь, когда мы вызываем show, this не указывает на obj, что и приводит к неожиданным результатам.

this в обработчиках событий

Когда вы добавляете обработчик события, this в нем обычно ссылается на элемент, на который установлено событие.

Пример:



Однако, если вы используете стрелочную функцию как обработчик, this будет указывать на родительский контекст, а не на элемент.

Пример со стрелочной функцией:



Неявный и явный this

JavaScript различает неявный и явный this. Неявный this — это когда this определяется автоматически в зависимости от того, как функция была вызвана. Явный this — это когда вы используете методы call, apply или bind, чтобы явно указать, на что ссылается this.

Неявный this:

const car = {
    brand: 'Ford',
    showBrand() {
        console.log(this.brand);
    },
};

car.showBrand(); // Ford

Явный this:

function showBrand() {
    console.log(this.brand);
}

const myCar = { brand: 'Toyota' };

showBrand.call(myCar); // Toyota

Пусть ваш код будет ясным, а this под контролем. Помни, что this — штука капризная, но, как любой хороший инструмент, в умелых руках творит чудеса.

Больше про языки программирования эксперты OTUS рассказывают в рамках практических онлайн-курсов. С полным каталогом курсов можно ознакомиться по ссылке.

© Habrahabr.ru