Контекст выполнения и this
this — одна из самых запутанных концепций JavaScript, потому что его значение определяется не местом определения функции, а способом её вызова. Это фундаментально отличается от замыканий, где переменные привязаны к месту создания функции.
Содержание
- Как определяется
this - Глобальный контекст и обычный вызов
- Метод объекта
- Конструктор
call,apply,bind- Потеря
this - Стрелочные функции
thisв классахthisв обработчиках событий React- Ключевые тезисы для интервью
- Заключение
- Полезные ссылки
Как определяется this
Значение this зависит от контекста вызова:
| Ситуация | Значение this |
|---|---|
| Глобальный контекст | window в браузере, global в Node.js |
| Обычный вызов функции | window/global в нестрогом режиме, undefined в строгом |
| Метод объекта | Сам объект |
Конструктор (new) | Новый создаваемый объект |
call / apply / bind | Явно переданный объект |
| Стрелочная функция | this из внешнего лексического окружения |
Важное правило: this вычисляется в момент вызова, а не в момент объявления функции.
Глобальный контекст и обычный вызов
В глобальной области this указывает на глобальный объект:
console.log(this); // window (браузер) или global (Node.js)
При обычном вызове функции поведение зависит от строгого режима:
function example() {
console.log(this);
}
example(); // window / global в нестрогом режиме
'use strict';
function example() {
console.log(this);
}
example(); // undefined
В строгом режиме ("use strict") this не подменяется глобальным объектом, а остаётся undefined.
Метод объекта
Когда функция вызывается как метод объекта, this указывает на объект перед точкой:
const user = {
name: 'Alice',
greet() {
console.log(`Hello, ${this.name}`);
},
};
user.greet(); // "Hello, Alice"
Но стоит передать метод как callback, как this теряется:
const greet = user.greet;
greet(); // "Hello, undefined" (или "Hello, " + window.name)
Теперь функция вызывается без контекста объекта, поэтому this указывает на глобальный объект или undefined в строгом режиме.
Конструктор
При вызове функции с new создаётся новый объект, и this указывает на него:
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // "Alice"
Если забыть new, this будет указывать на глобальный объект (в нестрогом режиме), что приведёт к созданию глобальной переменной:
const bob = Person('Bob');
console.log(bob); // undefined
console.log(name); // "Bob" — создано глобальное свойство!
В классах такая ошибка невозможна: если вызвать класс без new, будет ошибка TypeError.
call, apply, bind
Эти методы позволяют явно управлять this.
call
Вызывает функцию с указанным this и аргументами через запятую:
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
const user = { name: 'Alice' };
greet.call(user, 'Hello'); // "Hello, Alice"
apply
Работает как call, но аргументы передаются массивом:
greet.apply(user, ['Hi']); // "Hi, Alice"
bind
Возвращает новую функцию с навсегда привязанным this:
const greetUser = greet.bind(user);
greetUser('Hey'); // "Hey, Alice"
bind полезен, когда нужно передать метод как callback:
setTimeout(user.greet.bind(user), 100); // "Hello, Alice"
Потеря this
Самая частая ошибка — потеря контекста при передаче метода как callback:
const user = {
name: 'Alice',
greet() {
console.log(`Hello, ${this.name}`);
},
};
setTimeout(user.greet, 1000); // "Hello, undefined"
Почему? setTimeout вызывает переданную функцию без контекста объекта. user.greet — это просто ссылка на функцию, объект теряется.
Способы исправить
1. Обернуть в стрелочную функцию:
setTimeout(() => user.greet(), 1000); // "Hello, Alice"
2. Использовать bind:
setTimeout(user.greet.bind(user), 1000); // "Hello, Alice"
3. Сохранить this в переменную (устаревший подход):
const self = this;
setTimeout(function () {
self.greet();
}, 1000);
Стрелочные функции
Стрелочные функции не имеют собственного this. Они берут this из внешнего лексического окружения в момент создания.
const user = {
name: 'Alice',
greet: () => {
console.log(`Hello, ${this.name}`);
},
};
user.greet(); // "Hello, undefined"
Здесь стрелочная функция определена в глобальном контексте, поэтому this указывает на глобальный объект.
Стрелочные функции полезны внутри методов, когда нужно сохранить this из внешнего контекста:
const user = {
name: 'Alice',
greetDelayed() {
setTimeout(() => {
console.log(`Hello, ${this.name}`); // this из greetDelayed
}, 1000);
},
};
user.greetDelayed(); // "Hello, Alice"
Внутри setTimeout стрелочная функция «захватывает” this из greetDelayed, который указывает на user.
Разница между обычной и стрелочной функцией в методе
const obj = {
value: 42,
regular() {
console.log(this.value); // 42
},
arrow: () => {
console.log(this.value); // undefined (this из внешнего окружения)
},
};
obj.regular(); // 42
obj.arrow(); // undefined
Обычная функция получает this от способа вызова, стрелочная — от места создания.
this в классах
Обычные методы
В методах класса this указывает на экземпляр:
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}`);
}
}
const alice = new Person('Alice');
alice.greet(); // "Hello, Alice"
Потеря this в методах класса
Если передать метод класса как callback, this потеряется:
const greet = alice.greet;
greet(); // TypeError: Cannot read properties of undefined
Привязка в конструкторе
В классовых компонентах React методы обычно привязывали в конструкторе:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
Стрелочные методы классов
Современный способ — использовать стрелочную функцию как поле класса. Она не имеет собственного this и захватывает this экземпляра:
class Counter extends React.Component {
state = { count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
Это удобно, но создаёт новую функцию на каждом экземпляре, а не в прототипе.
this в обработчиках событий React
Функциональные компоненты
В функциональных компонентах проблема this отсутствует, потому что нет методов объекта. Но важно помнить о замыканиях:
function User({ name }) {
function handleClick() {
console.log(name); // замыкание, а не this
}
return <button onClick={handleClick}>Show name</button>;
}
Inline-стрелки
function User({ name }) {
return <button onClick={() => console.log(name)}>Show name</button>;
}
Inline-обработчики удобны, но создают новую функцию на каждом рендере. Для простых случаев это приемлемо, для сложных — лучше useCallback.
Пример с this в классовом компоненте
class Search extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
e.preventDefault();
console.log(this.props.query);
}
render() {
return <form onSubmit={this.handleSubmit}>...</form>;
}
}
Если не привязать handleSubmit, React вызовет его как обычную функцию, и this будет undefined в строгом режиме.
Ключевые тезисы для интервью
- Значение
thisопределяется контекстом вызова функции, а не местом её объявления. - В глобальном контексте
thisуказывает на глобальный объект, при обычном вызове функции в строгом режиме —undefined. - При вызове функции как метода объекта
thisуказывает на объект, стоящий перед точкой. - Конструктор с
newсоздаёт новый объект и привязываетthisк нему; вызов функции-конструктора безnewприводит к утечкеthisв глобальную область. callвызывает функцию с явно указаннымthisи аргументами через запятую,apply— с аргументами массивом,bind— возвращает новую функцию с навсегда привязаннымthis.- Потеря
thisпроисходит при передаче метода как callback — объект теряется, иthisстановитсяundefined; исправляется обёрткой в стрелочную функцию,bindили сохранениемthisв переменную. - Стрелочные функции не имеют собственного
thisи захватывают его из внешнего лексического окружения в момент создания. - В методах класса
thisуказывает на экземпляр, но при передаче метода как callback контекст теряется — это исправляется привязкой в конструкторе или стрелочными полями класса. - В классовых компонентах React методы нужно привязывать, потому что React вызывает обработчики как обычные функции, где
thisбудетundefinedв строгом режиме. - Стрелочная функция внутри метода сохраняет правильный
this, потому что захватывает его из окружающего метода в момент определения.
Заключение
this — одна из ключевых концепций JavaScript, значение которой определяется способом вызова функции, а не местом её определения. Стрелочные функции решают большинство проблем с потерей контекста, потому что захватывают this из внешнего окружения. В классах и React-компонентах важно явно привязывать методы через bind в конструкторе или использовать стрелочные поля класса. Понимание this, call/apply/bind и разницы между обычной и стрелочной функциями — обязательный навык для уверенной работы с JavaScript.