Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Ключове слово this». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`this`, це посилання на контекст виконання функції, тобто на об'єкт, у контексті якого функцію було викликано.** Значення `this` не визначається під час оголошення функції: воно призначається в момент виклику і залежить від способу виклику, а не від місця в коді. Виклик через крапку `obj.method()` дає сам `obj`; звичайний виклик `fn()` дає `undefined` у строгому режимі й `window` у нестрогому; виклик з `new` дає новий екземпляр; `call`, `apply` і `bind` задають `this` явно; обробник події отримує DOM-елемент. Окремий випадок, це стрілкові функції: вони не мають власного `this` і беруть його з зовнішнього лексичного оточення. ```javascript const user = { name: 'Oleh', sayHi() { console.log(this.name); } }; user.sayHi(); // 'Oleh', called through the dot const fn = user.sayHi; fn(); // undefined, the context is lost fn.call(user); // 'Oleh', this is set explicitly ``` **Ключове:** `this` визначає місце виклику, а не місце оголошення, крім стрілкових функцій, де `this` лексичний.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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` контекст не змінює.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.