Слово this: управление контекстом выполнения в JavaScript
Привет, Хабр! Сегодня мы поговорим о 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 рассказывают в рамках практических онлайн-курсов. С полным каталогом курсов можно ознакомиться по ссылке.