Ключове слово this
this, це посилання на контекст виконання функції, тобто на об'єкт, у контексті якого функцію було викликано. Значення this призначається під час виклику і залежить від способу виклику, а не від того, де функцію написано.
Теорія
TL;DR
thisне визначається під час оголошення функції, воно призначається в runtime, у момент виклику.- Виклик через крапку
obj.method()прив'язуєthisдоobj. - Звичайний виклик
fn()даєundefinedу строгому режимі іwindowу нестрогому. newстворює новий об'єкт і робить його значеннямthis.call,applyіbindзадаютьthisявно.- Стрілкові функції власного
thisне мають, вони успадковують його з зовнішньої області.
Швидкий приклад
function showName() {
console.log(this.name);
}
const user = { name: 'Oleh', showName };
user.showName(); // 'Oleh', the object before the dot
showName.call({ name: 'Maria' }); // 'Maria', this is set explicitly
const detached = user.showName;
detached(); // TypeError in strict mode, the context is lostЯк визначається this
Простими словами:
thisне визначається під час оголошення функції;- воно призначається в момент виклику (runtime);
- і залежить від способу виклику, а не від того, де функцію написано.
Глобальний контекст. Поза будь-якою функцією:
console.log(this);- у браузері це
window; - у Node.js всередині CommonJS-модуля це
{}, порожній об'єкт (module.exports); - в ES-модулі це
undefined.
Усередині звичайної функції.
function showThis() {
console.log(this);
}
showThis();Якщо у функції немає «власника», тобто її викликано не як obj.method():
- у нестрогому режимі
thisдорівнюєwindow; - у строгому режимі (
'use strict')thisдорівнюєundefined.
Код усередині класів і ES-модулів завжди строгий, тому там працює другий варіант.
Метод об'єкта і втрата контексту
Коли функцію викликано через крапку, this вказує на об'єкт перед крапкою:
const user = {
name: 'Oleh',
sayHi() {
console.log(`Привіт, я ${this.name}`);
}
};
user.sayHi(); // Привіт, я OlehАле сам метод не «пам'ятає» свій об'єкт. Достатньо зберегти посилання на нього в змінну, і зв'язок зникає:
const user = {
name: 'Oleh',
sayHi() {
console.log(this.name);
}
};
const fn = user.sayHi;
fn(); // undefined in sloppy mode, TypeError in strict modeТак само контекст губиться, коли метод передають як колбек: setTimeout(user.sayHi, 1000), arr.map(user.sayHi), element.addEventListener('click', user.sayHi). Функція йде далі сама по собі, без об'єкта перед крапкою.
Як зафіксувати this
Спосіб 1: bind. Повертає нову функцію з назавжди прив'язаним контекстом.
const boundFn = user.sayHi.bind(user);
boundFn(); // Привіт, я Oleh
setTimeout(boundFn, 1000); // still worksСпосіб 2: стрілкова функція. Стрілки не мають власного this і беруть його з зовнішньої області:
const user = {
name: 'Oleh',
sayHiLater() {
setTimeout(() => {
console.log(this.name); // 'Oleh', this comes from sayHiLater
}, 1000);
}
};
user.sayHiLater();Зворотний бік тієї самої властивості: стрілка не годиться як метод об'єкта, бо бере this не з виклику, а з місця оголошення:
const user = {
name: 'Oleh',
showThis: () => console.log(this)
};
user.showThis(); // window in a browser script, undefined in a moduleСпосіб 3: явне керування через call, apply, bind.
| Метод | Що робить | Коли виконується |
|---|---|---|
call(thisArg, a, b) | викликає функцію із заданим this, аргументи через кому | одразу |
apply(thisArg, [a, b]) | те саме, але аргументи масивом | одразу |
bind(thisArg) | повертає нову функцію з прив'язаним this | пізніше |
function greet() {
console.log(`Привіт, ${this.name}`);
}
const user = { name: 'Oleh' };
greet.call(user); // Привіт, Oleh
greet.apply(user); // Привіт, Oleh
greet.bind(user)(); // Привіт, Olehnew, класи та обробники подій
Виклик з new. Якщо функцію викликано через new, this вказує на новий створюваний об'єкт:
function User(name) {
this.name = name;
}
const oleh = new User('Oleh');
console.log(oleh.name); // OlehЩо відбувається під час виклику через new:
- створюється порожній об'єкт
{}; - він прив'язується до
this, а його прототипом стаєUser.prototype; - виконується тіло функції;
- повертається цей об'єкт, якщо функція явно не повернула інший об'єкт.
У класах this працює так само, як у методах звичайних об'єктів:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Привіт, я ${this.name}`);
}
}
const oleh = new User('Oleh');
oleh.sayHi(); // Привіт, я OlehВ обробниках подій у браузері this вказує на DOM-елемент, на якому висить слухач:
button.addEventListener('click', function () {
console.log(this); // the button element
});Якщо ж передати стрілкову функцію, this буде не кнопкою, а зовнішнім контекстом:
button.addEventListener('click', () => {
console.log(this); // not the button, but the outer context
});У такому разі беріть event.currentTarget, це надійніше за this і працює однаково для обох видів функцій.
Підсумкова таблиця
| Контекст | Що буде в this |
|---|---|
| Глобальний (у браузері) | window |
| У звичайній функції | undefined у строгому режимі, window у нестрогому |
| У методі об'єкта | Сам об'єкт |
| У стрілковій функції | this зовнішньої області |
| У конструкторі або класі | Новий екземпляр |
| В обробнику події | DOM-елемент, на якому висить слухач |
Через call, apply, bind | Указаний вручну об'єкт |
Типові помилки
- Думати, що
thisзалежить від місця оголошення функції. Для звичайних функцій значення дає саме місце виклику. - Передавати метод як колбек без прив'язки:
setTimeout(user.sayHi, 1000). Потрібноuser.sayHi.bind(user)або стрілка() => user.sayHi(). - Використовувати стрілку як метод об'єкта і дивуватися, чому
this.nameдорівнюєundefined. - Вішати стрілку на
addEventListenerі чекати, щоthisбуде елементом. Беріть звичайну функцію абоevent.currentTarget. - Забувати
newу виклику функції-конструктора. У строгому режимі будеTypeError, у нестрогому властивості потраплять у глобальний об'єкт. - Намагатися перепризначити
thisу вже прив'язаної функції. Повторнийbindабоcallна результатbindконтекст не змінює.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.