Skip to main content

Ключове слово this

this, це посилання на контекст виконання функції, тобто на об'єкт, у контексті якого функцію було викликано. Значення this призначається під час виклику і залежить від способу виклику, а не від того, де функцію написано.

Теорія

TL;DR

  • this не визначається під час оголошення функції, воно призначається в runtime, у момент виклику.
  • Виклик через крапку obj.method() прив'язує this до obj.
  • Звичайний виклик fn() дає undefined у строгому режимі і window у нестрогому.
  • new створює новий об'єкт і робить його значенням this.
  • call, apply і bind задають this явно.
  • Стрілкові функції власного this не мають, вони успадковують його з зовнішньої області.

Швидкий приклад

javascript
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);
  • і залежить від способу виклику, а не від того, де функцію написано.

Глобальний контекст. Поза будь-якою функцією:

javascript
console.log(this);
  • у браузері це window;
  • у Node.js всередині CommonJS-модуля це {}, порожній об'єкт (module.exports);
  • в ES-модулі це undefined.

Усередині звичайної функції.

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

Якщо у функції немає «власника», тобто її викликано не як obj.method():

  • у нестрогому режимі this дорівнює window;
  • у строгому режимі ('use strict') this дорівнює undefined.

Код усередині класів і ES-модулів завжди строгий, тому там працює другий варіант.

Метод об'єкта і втрата контексту

Коли функцію викликано через крапку, this вказує на об'єкт перед крапкою:

javascript
const user = { name: 'Oleh', sayHi() { console.log(`Привіт, я ${this.name}`); } }; user.sayHi(); // Привіт, я Oleh

Але сам метод не «пам'ятає» свій об'єкт. Достатньо зберегти посилання на нього в змінну, і зв'язок зникає:

javascript
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. Повертає нову функцію з назавжди прив'язаним контекстом.

javascript
const boundFn = user.sayHi.bind(user); boundFn(); // Привіт, я Oleh setTimeout(boundFn, 1000); // still works

Спосіб 2: стрілкова функція. Стрілки не мають власного this і беруть його з зовнішньої області:

javascript
const user = { name: 'Oleh', sayHiLater() { setTimeout(() => { console.log(this.name); // 'Oleh', this comes from sayHiLater }, 1000); } }; user.sayHiLater();

Зворотний бік тієї самої властивості: стрілка не годиться як метод об'єкта, бо бере this не з виклику, а з місця оголошення:

javascript
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пізніше
javascript
function greet() { console.log(`Привіт, ${this.name}`); } const user = { name: 'Oleh' }; greet.call(user); // Привіт, Oleh greet.apply(user); // Привіт, Oleh greet.bind(user)(); // Привіт, Oleh

new, класи та обробники подій

Виклик з new. Якщо функцію викликано через new, this вказує на новий створюваний об'єкт:

javascript
function User(name) { this.name = name; } const oleh = new User('Oleh'); console.log(oleh.name); // Oleh

Що відбувається під час виклику через new:

  1. створюється порожній об'єкт {};
  2. він прив'язується до this, а його прототипом стає User.prototype;
  3. виконується тіло функції;
  4. повертається цей об'єкт, якщо функція явно не повернула інший об'єкт.

У класах this працює так само, як у методах звичайних об'єктів:

javascript
class User { constructor(name) { this.name = name; } sayHi() { console.log(`Привіт, я ${this.name}`); } } const oleh = new User('Oleh'); oleh.sayHi(); // Привіт, я Oleh

В обробниках подій у браузері this вказує на DOM-елемент, на якому висить слухач:

javascript
button.addEventListener('click', function () { console.log(this); // the button element });

Якщо ж передати стрілкову функцію, this буде не кнопкою, а зовнішнім контекстом:

javascript
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 контекст не змінює.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.