[Medium] 📄 Привязка this
1. Что такое this в JavaScript?
Что такое
thisв JavaScript?
this — это ключевое слово в JavaScript, которое указывает на объект контекста выполнения функции.
Значение this зависит от того, как функция вызвана, а не от того, где она определена.
Правила привязки this
В JavaScript существует четыре правила привязки this (от высшего к низшему приоритету):
- Привязка new: функция вызвана с
new - Явная привязка:
call,applyилиbindявно устанавливаютthis - Неявная привязка: вызывается как метод объекта
- Привязка по умолчанию: поведение по умолчанию в других местах вызова
2. Объясните различие this в разных контекстах
Объясните, как ведёт себя
thisв разных контекстах.
1. this в глобальном контексте
console.log(this); // браузер: window, Node.js: global
function globalFunction() {
console.log(this); // не strict: window/global, strict: undefined
}
globalFunction();
'use strict';
function strictFunction() {
console.log(this); // undefined
}
strictFunction();
2. this в обычных функциях
Для обычных функций this зависит от места вызова:
function regularFunction() {
console.log(this);
}
// прямой вызов
regularFunction(); // window (не strict) или undefined (strict)
// вызов как метод
const obj = {
method: regularFunction,
};
obj.method(); // obj
// call/apply/bind
const customObj = { name: 'Custom' };
regularFunction.call(customObj); // customObj
3. this в стрелочных функциях
Стрелочные функции не имеют собственного this.
Они наследуют this из внешней лексической области.
const obj = {
name: 'Object',
// обычный метод
regularMethod: function () {
console.log('regularMethod this:', this); // obj
// внутренняя обычная функция: this меняется
function innerRegular() {
console.log('innerRegular this:', this); // window/undefined
}
innerRegular();
// внутренняя стрелочная функция: this наследуется
const innerArrow = () => {
console.log('innerArrow this:', this); // obj
};
innerArrow();
},
// стрелочная функция как свойство объекта
arrowMethod: () => {
console.log('arrowMethod this:', this); // window/global лексическая область
},
};
obj.regularMethod();
obj.arrowMethod();
4. this в методах объекта
const person = {
name: 'John',
age: 30,
// обычная функция: this -> person
greet: function () {
console.log(`Hello, I'm ${this.name}`); // "Hello, I'm John"
},
// сокращённый синтаксис ES6: this -> person
introduce() {
console.log(`I'm ${this.name}, ${this.age} years old`);
},
// стрелочная функция: this наследуется из внешней области
arrowGreet: () => {
console.log(`Hello, I'm ${this.name}`); // обычно undefined для name
},
};
person.greet();
person.introduce();
person.arrowGreet();
5. this в функциях-конструкторах
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function () {
console.log(`Hello, I'm ${this.name}`);
};
}
const john = new Person('John', 30);
john.greet();
console.log(john.name); // "John"
6. this в классах
class Person {
constructor(name) {
this.name = name;
}
// обычный метод: this -> экземпляр
greet() {
console.log(`Hello, I'm ${this.name}`);
}
// стрелочная функция в поле класса: this постоянно привязан к экземпляру
arrowGreet = () => {
console.log(`Hi, I'm ${this.name}`);
};
}
const john = new Person('John');
john.greet(); // "Hello, I'm John"
// извлечение метода теряет this
const greet = john.greet;
greet(); // ошибка в strict mode
// стрелочное поле сохраняет this
const arrowGreet = john.arrowGreet;
arrowGreet(); // "Hi, I'm John"
3. Задачи: что будет выведено?
Задачи: что выведет следующий код?
Задача 1: метод объекта vs стрелочная функция
const obj = {
name: 'Object',
regularFunc: function () {
console.log('A:', this.name);
},
arrowFunc: () => {
console.log('B:', this.name);
},
};
obj.regularFunc();
obj.arrowFunc();
Нажмите, чтобы увидеть ответ
// A: Object
// B: undefined
Объяснение:
regularFuncвызвана какobj.regularFunc(), поэтомуthisравенobjarrowFuncне имеет собственногоthis; наследует внешний/глобальный лексическийthis
Задача 2: передача функции как аргумента
const person = {
name: 'John',
greet: function () {
console.log(`Hello, ${this.name}`);
},
};
person.greet(); // 1
const greet = person.greet;
greet(); // 2
setTimeout(person.greet, 1000); // 3
Нажмите, чтобы увидеть ответ
// 1: "Hello, John"
// 2: "Hello, undefined" или ошибка в strict mode
// 3: "Hello, undefined" или ошибка в strict mode
Объяснение:
person.greet()-> неявная привязка,thisравенperson- Извлечённая функция ->
thisпотерян - Callback, переданный в
setTimeout->thisне равенperson
Задача 3: вложенные функции
const obj = {
name: 'Outer',
method: function () {
console.log('A:', this.name);
function inner() {
console.log('B:', this.name);
}
inner();
const arrow = () => {
console.log('C:', this.name);
};
arrow();
},
};
obj.method();
Нажмите, чтобы увидеть ответ
// A: Outer
// B: undefined
// C: Outer
Объяснение:
A:methodвызван отobjB:inner— обычный прямой вызовC: стрелочная функция наследуетthisвнешнегоmethod
Задача 4: setTimeout и стрелочная функция
const obj = {
name: 'Object',
method1: function () {
setTimeout(function () {
console.log('A:', this.name);
}, 100);
},
method2: function () {
setTimeout(() => {
console.log('B:', this.name);
}, 100);
},
};
obj.method1();
obj.method2();
Нажмите, чтобы увидеть ответ
// A: undefined
// B: Object
Объяснение:
A: обычный callback вsetTimeoutтеряет контекст методаB: стрелочный callback наследуетthisизmethod2
Задача 5: сложная привязка this
const obj1 = {
name: 'obj1',
getThis: function () {
return this;
},
};
const obj2 = {
name: 'obj2',
};
console.log('A:', obj1.getThis().name);
const getThis = obj1.getThis;
console.log('B:', getThis() === window); // предполагается браузер
obj2.getThis = obj1.getThis;
console.log('C:', obj2.getThis().name);
const boundGetThis = obj1.getThis.bind(obj2);
console.log('D:', boundGetThis().name);
Нажмите, чтобы увидеть ответ
// A: obj1
// B: true
// C: obj2
// D: obj2
Объяснение:
A: вызван отobj1B: прямой вызов использует привязку по умолчанию (window в не-strict браузере)C: вызван отobj2D: явно привязан черезbind(obj2)
Задача 6: конструктор и prototype
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
console.log(`Hello, I'm ${this.name}`);
};
Person.prototype.delayedGreet = function () {
setTimeout(function () {
console.log('A:', this.name);
}, 100);
};
Person.prototype.arrowDelayedGreet = function () {
setTimeout(() => {
console.log('B:', this.name);
}, 100);
};
const john = new Person('John');
john.delayedGreet();
john.arrowDelayedGreet();
Нажмите, чтобы увидеть ответ
// A: undefined
// B: John
Объяснение:
A: обычный callback в setTimeout использует глобальную/стандартную привязкуB: стрелочный callback наследуетthisэкземпляра
Задача 7: глобальная переменная vs свойство объекта
var name = 'jjjj';
var obj = {
a: function () {
name = 'john';
console.log(this.name);
},
};
obj.a();
Нажмите, чтобы увидеть ответ
// undefined
Объяснение:
Ключ — в разнице между глобальными переменными и свойствами объекта.
thisвobj.a()указывает наobjname = 'john'(без объявления) обновляет глобальную переменнуюthis.nameчитаетobj.name- У
objнет свойстваname, поэтому этоundefined
Порядок выполнения:
// начальное состояние
window.name = 'jjjj';
obj = {
a: function () {
/* ... */
},
// у obj нет свойства name
};
obj.a();
↓
window.name = 'john'; // глобальное значение изменено
this.name; // равно obj.name
obj.name; // undefined
Если хотите 'john', присваивайте через this.name = 'john'.
var obj = {
a: function () {
this.name = 'john';
console.log(this.name); // 'john'
},
};
obj.a();
console.log(obj.name); // 'john'
Задача 8: ловушка глобальной переменной (расширенная)
var name = 'global';
var obj = {
name: 'object',
a: function () {
name = 'modified';
console.log('1:', name); // глобальная переменная
console.log('2:', this.name); // свойство объекта
},
};
obj.a();
console.log('3:', name); // глобальная переменная
console.log('4:', obj.name); // свойство объекта
Нажмите, чтобы увидеть ответ
// 1: modified
// 2: object
// 3: modified
// 4: object
Ключевой момент:
nameбезthis.-> глобальная переменнаяthis.name-> свойство объекта- Это разные значения
4. Как сохранить this в callback?
Как сохранить
thisвнутри callback-функций?
Способ 1: стрелочная функция
const obj = {
name: 'Object',
method: function () {
setTimeout(() => {
console.log(this.name); // "Object"
}, 1000);
},
};
obj.method();
Способ 2: bind()
const obj = {
name: 'Object',
method: function () {
setTimeout(
function () {
console.log(this.name); // "Object"
}.bind(this),
1000
);
},
};
obj.method();
Способ 3: сохранение this в переменную (устаревший паттерн)
const obj = {
name: 'Object',
method: function () {
const self = this;
setTimeout(function () {
console.log(self.name); // "Object"
}, 1000);
},
};
obj.method();
Способ 4: call() / apply()
function greet() {
console.log(`Hello, I'm ${this.name}`);
}
const person1 = { name: 'John' };
const person2 = { name: 'Jane' };
greet.call(person1); // "Hello, I'm John"
greet.apply(person2); // "Hello, I'm Jane"
5. Распространённые подводные камни this
Распространённые подводные камни
this
Подводный камень 1: извлечение метода объекта
const obj = {
name: 'Object',
greet: function () {
console.log(this.name);
},
};
obj.greet(); // ✅ "Object"
const greet = obj.greet;
greet(); // ❌ this потерян
const boundGreet = obj.greet.bind(obj);
boundGreet(); // ✅ "Object"
Подводный камень 2: this в обработчиках событий
const button = document.querySelector('button');
const obj = {
name: 'Object',
// ❌ стрелочная функция здесь не привязывается к button
handleClick1: () => {
console.log(this); // window/global лексическая область
},
// ✅ обычная функция в обработчике получает элемент события как this
handleClick2: function () {
console.log(this); // элемент button
},
// ✅ используйте стрелочную обёртку, когда нужен this объекта внутри callback
handleClick3: function () {
button.addEventListener('click', () => {
console.log(this.name); // "Object"
});
},
};
Подводный камень 3: callback в методах массивов
const obj = {
name: 'Object',
items: [1, 2, 3],
// ❌ обычный callback теряет this
processItems1: function () {
this.items.forEach(function (item) {
console.log(this.name, item);
});
},
// ✅ стрелочный callback сохраняет лексический this
processItems2: function () {
this.items.forEach((item) => {
console.log(this.name, item);
});
},
// ✅ используйте thisArg
processItems3: function () {
this.items.forEach(function (item) {
console.log(this.name, item);
}, this);
},
};
6. Сводка правил привязки this
Сводка правил привязки
this
Приоритет (высокий -> низкий)
// 1. привязка new (высший)
function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john.name); // "John"
// 2. явная привязка (call/apply/bind)
function greet() {
console.log(this.name);
}
const obj = { name: 'Object' };
greet.call(obj); // "Object"
// 3. неявная привязка (метод объекта)
const obj2 = {
name: 'Object2',
greet: greet,
};
obj2.greet(); // "Object2"
// 4. привязка по умолчанию (низший)
greet(); // undefined (strict) или глобальное name (не-strict)
Обычная функция vs стрелочная функция (Function vs Arrow Function)
| Характеристика | Функция | Стрелочная функция |
|---|---|---|
Имеет собственный this | ✅ Да | ❌ Нет |
this определяется | Местом вызова | Лексической областью определения |
call/apply/bind могут изменить this | ✅ Да | ❌ Нет |
| Может быть конструктором | ✅ Да | ❌ Нет |
Имеет arguments | ✅ Да | ❌ Нет |
| Лучше для | Методов объектов, конструкторов | Callback, наследование внешнего this |
Фраза для запоминания
"Стрелочная наследует, обычная зависит от вызова."
- Стрелочная функция:
thisнаследуется из внешней лексической области- Обычная функция:
thisопределяется в момент вызова местом вызова
7. Лучшие практики (Best Practices)
Лучшие практики
✅ Рекомендуется
// 1. Используйте обычную функцию или сокращённый синтаксис для методов объектов
const obj = {
name: 'Object',
// ✅ хорошо
greet: function () {
console.log(this.name);
},
// ✅ хорошо
introduce() {
console.log(this.name);
},
};
// 2. Используйте стрелочные функции для callback, которые должны сохранить внешний this
class Component {
constructor() {
this.name = 'Component';
}
mount() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
}
// 3. Используйте обычную функцию, когда нужен динамический this
const button = {
label: 'Click me',
handleClick: function () {
console.log(this); // элемент события / объект-получатель
},
};
❌ Не рекомендуется
// 1. Избегайте стрелочных функций как методов объектов
const obj = {
name: 'Object',
greet: () => {
console.log(this.name); // undefined в большинстве случаев
},
};
// 2. Избегайте стрелочных функций как конструкторов
const Person = (name) => {
this.name = name; // неправильно
};
// 3. Избегайте стрелочных функций, когда нужен объект arguments
const sum = () => {
console.log(arguments); // ReferenceError
};