Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Ключове слово this у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`this`** - посилання на об'єкт, який викликає функцію. Значення визначається в момент виклику. Звичайні функції отримують `this` з місця виклику. Стрілкові успадковують його зі свого scope при створенні. `call`, `apply`, `bind` встановлюють `this` явно. ```javascript const obj = { name: 'Alice', fn() { return this.name; } }; obj.fn(); // "Alice" const f = obj.fn; f(); // undefined (strict mode) ``` **Головне правило:** `this` визначає той, хто викликає функцію, якщо це не стрілкова функція.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`this`** - це посилання на об'єкт, який зараз викликає функцію. Значення визначається в момент виклику, а не в момент оголошення. ## Теорія ### TL;DR - `this` - як дисплей "хто дзвонить": показує того, хто викликає функцію, а не саму функцію - Звичайні функції отримують `this` з місця виклику; стрілкові фіксують його зі scope, де були написані - `obj.method()` встановлює `this` в `obj`; передача методу як callback губить прив'язку - Звичайні функції для методів об'єкта, стрілкові для callback і вкладених функцій - `call`, `apply`, `bind` дозволяють встановити `this` вручну ### Швидкий приклад ```javascript const obj = { name: 'Alice', regular: function() { console.log(this.name); }, // this = obj arrow: () => console.log(this.name) // this = зовнішній scope }; obj.regular(); // "Alice" obj.arrow(); // undefined (strict mode) ``` Дві функції, один об'єкт, протилежні результати. Виклик `obj.regular()` прив'язує `this` до `obj`. Стрілкова функція ігнорує це повністю. Вона зафіксувала `this` зовнішнього scope в момент створення, і нічого це не змінить. ### Головна різниця Звичайні функції визначають `this` у момент виклику. `obj.method()` - `this` є `obj`; `fn()` сама по собі - `this` є глобальний об'єкт або `undefined` у strict mode. Стрілкові функції повністю пропускають цей механізм. Вони фіксують `this` батьківського scope в момент створення, і це неможливо змінити. Виклики `call`, `apply` або `bind` не мають жодного ефекту на `this` стрілкової функції. ### Коли що використовувати - **Метод об'єкта, який читає або змінює дані екземпляра**: звичайна функція - **Callback для `setTimeout`, `forEach` або обробників подій**: стрілкова, зберігає зовнішній `this` - **Конструктор з `new`**: тільки звичайна функція. Стрілкові кидають `TypeError: not a constructor` - **Обробник подій у класі**: arrow class field (`handleClick = () => {}`) позбуває від ручного `bind` у конструкторі - **Утилітна функція без об'єктного контексту**: стрілкова, немає ризику випадково звернутись до глобального `this` ### Як `this` визначається під капотом JavaScript-рушій прикріплює execution context до кожного виклику функції. Для звичайних функцій рушій перевіряє місце виклику: `obj.fn()` встановлює `this` як `obj`; `fn()` без об'єкта повертається до глобального (або `undefined` у strict mode). `bind()` повертає нову функцію з жорстко закріпленим `this`, який не можна перевизначити при виклику. Стрілкові функції взагалі не беруть участі в цьому механізмі. Вони захоплюють `this` батьківського scope в момент створення. Одна деталь середовища: у ESM-модулях глобальний `this` завжди `undefined`. У CommonJS він вказує на `module.exports`. Це варто пам'ятати при відладці Node.js-утиліт. ### Типові помилки **Стрілкова функція як метод об'єкта:** ```javascript const user = { name: 'Bob', greet: () => console.log(this.name) // помилка: стрілка не має прив'язки до user }; user.greet(); // undefined ``` Стрілкові функції не мають доступу до об'єкта, в якому вони оголошені. Для будь-якого методу, що читає властивості свого об'єкта, потрібна звичайна функція. **Втрата `this` у callback:** ```javascript const logger = { prefix: '[INFO]', log: function() { console.log(this.prefix); // undefined, this = global у setTimeout } }; setTimeout(logger.log, 0); // відірвано від об'єкта setTimeout(() => logger.log(), 0); // працює: стрілка викликає метод через об'єкт ``` Передача `logger.log` у `setTimeout` витягує функцію з об'єкта - прив'язка губиться. Стрілка-обгортка виправляє це, бо викликає метод безпосередньо на об'єкті, а не передає голе посилання. Це найчастіша помилка з `this`, яку я бачу на code review, зокрема у production-коді Express, де посилання на метод потрапляє до обробника черги і падає без очевидної помилки. **Неприв'язаний обробник події:** ```javascript function Counter() { this.count = 0; document.querySelector('button').addEventListener('click', function() { this.count++; // this = елемент button, не екземпляр Counter }); } ``` Виправлення: стрілкова функція або `.bind(this)` при додаванні слухача. **`new` зі стрілковою функцією:** ```javascript const Ctor = () => {}; new Ctor(); // TypeError: Ctor is not a constructor ``` У стрілкових функцій немає внутрішнього слоту `[[Construct]]`. Конструктором може бути тільки звичайна функція. ### Де зустрічається в реальному коді - **React class components**: `this.setState()` і `this.props` залежать від того, щоб `this` вказував на екземпляр компонента. Arrow class fields (`handleClick = () => {}`) замінили ручний `bind` у конструкторах і досі часто зустрічаються в legacy-коді - **Express**: обробники помилок у класах потребують `bind` або стрілкових методів, щоб `this` залишався на екземплярі - **Node.js Transform streams**: `this.push(chunk)` усередині `_transform` працює тому, що Node викликає метод на екземплярі потоку - **Lodash**: `_.bind(fn, context)` і `_.bindAll(obj, methods)` існують саме для виправлення `this` у коді з великою кількістю callback-ів ### Питання на співбесіді **Q:** Що таке `this` всередині `setTimeout(fn, 0)`? **A:** Глобальний об'єкт, або `undefined` у strict mode. `setTimeout` викликає функцію без об'єктного контексту, тому прив'язка губиться. **Q:** В чому різниця між `call`, `apply` і `bind`? **A:** Усі три встановлюють `this` явно. `call` і `apply` викликають функцію одразу: `call` приймає аргументи по одному, `apply` - масивом. `bind` повертає нову функцію з постійно встановленим `this`, без виклику. **Q:** Що таке `this` на верхньому рівні ESM-модуля? **A:** Завжди `undefined`. У CommonJS-модулях це `module.exports`. **Q:** Як `new` впливає на `this`? **A:** `new` створює порожній об'єкт, встановлює `this` на нього всередині конструктора і повертає цей об'єкт, якщо конструктор не повернув інший об'єкт явно. **Q:** Чи можна змінити `this` стрілкової функції через `call` або `bind`? **A:** Ні. Стрілкові функції ігнорують будь-який `this`, переданий через `call`, `apply` або `bind`. Лексична прив'язка постійна з моменту створення функції. **Q:** (Senior) Як V8 оптимізує код з `this`? **A:** V8 використовує inline caches (IC) для відстеження типу `this` на кожному місці виклику. Якщо тип стабільний між викликами (monomorphic), TurboFan агресивно оптимізує виклик. Якщо тип змінюється (polymorphic), V8 деоптимізує. Саме тому strict mode і стабільні форми об'єктів покращують продуктивність. ## Приклади ### Виклик методу проти відірваного виклику ```javascript const car = { speed: 60, accelerate: function() { this.speed += 10; console.log(this.speed); } }; car.accelerate(); // 70, this = car const fn = car.accelerate; fn(); // NaN у strict mode (undefined + 10) або змінює глобальну speed ``` Присвоєння методу у змінну відриває його від об'єкта. Тіло функції однакове в обох випадках. Різниться лише місце виклику, і саме воно визначає `this`. ### React клас з bind і arrow class field ```javascript class UserProfile extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; this.handleClick = this.handleClick.bind(this); // ручний bind для звичайного методу } handleClick() { this.setState({ count: this.state.count + 1 }); // this = екземпляр через bind } handleArrow = () => { this.setState({ count: this.state.count + 1 }); // this = екземпляр автоматично }; render() { return <button onClick={this.handleArrow}>{this.state.count}</button>; } } ``` Обидва підходи працюють. Arrow class field виглядає чистіше і не потребує конструктора. Прив'язаний через `bind` метод ділиться через прототип між екземплярами. На практиці більшість команд обирають стрілкові поля - різниця в пам'яті для UI-компонентів мізерна. ### Втрата `this` в Express middleware ```javascript app.use((req, res, next) => { const logger = { userAgent: req.get('User-Agent'), log: function() { console.log(this.userAgent); // undefined, this = global у setTimeout } }; setTimeout(logger.log, 0); // не працює setTimeout(() => logger.log(), 0); // працює: стрілка викликає метод через об'єкт next(); }); ``` Стрілка-обгортка `() => logger.log()` не передає посилання на функцію - вона викликає метод безпосередньо на об'єкті, зберігаючи прив'язку. Цей патерн часто зустрічається у middleware та асинхронному коді з чергами завдань.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.