Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Методи call, apply та bind у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**call**, **apply** та **bind** — методи `Function.prototype` для явного задання `this` при виклику функції. ```javascript greet.call(user, 'Hi', '!'); // виконується одразу, аргументи окремо greet.apply(user, ['Hi', '!']); // виконується одразу, аргументи масивом const fn = greet.bind(user, 'Hi'); // повертає нову функцію, виконується пізніше fn('!'); // "Hi, Alice!" ``` **Ключове:** `call` і `apply` виконуються негайно; `bind` повертає функцію з зафіксованим `this`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**call**, **apply** та **bind** — методи `Function.prototype`, які дозволяють явно задати `this` для будь-якого виклику функції, незалежно від того, де і як її визначено. ## Теорія ### TL;DR - Уявляй це як передачу функції конкретного об'єкта як її "власника" для цього виклику - `call` і `apply` запускають функцію одразу; `bind` повертає нову функцію з зафіксованим `this` - Єдина різниця між `call` і `apply` — формат аргументів: окремі значення чи масив - Правило вибору: треба виконати зараз — `call` або `apply`. Потрібна функція з постійним контекстом — `bind` - `bind` також дозволяє зафіксувати аргументи наперед (часткове застосування, partial application) ### Короткий приклад ```javascript const user = { name: 'Alice' }; function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.call(user, 'Hi', '!'); // "Hi, Alice!" greet.apply(user, ['Hello', '.']); // "Hello, Alice." const boundGreet = greet.bind(user, 'Hey'); boundGreet('!'); // "Hey, Alice!" ``` `call` і `apply` виконуються одразу з заданим `this`. `bind` повертає нову функцію, яку ти викликаєш пізніше, і `this` у ній вже зафіксований. ### Головна різниця `call` і `apply` викликають функцію негайно з фіксованим `this`. Відрізняються лише форматом аргументів: `call` приймає їх окремо, `apply` приймає масив. `bind` не викликає функцію одразу. Він створює новий функціональний об'єкт із постійно закріпленим `this` і необов'язковими зафіксованими аргументами, який ти викликаєш тоді, коли треба. ### Коли використовувати - Позичаєш метод одного об'єкта для іншого: `call` або `apply` - Треба передати динамічний масив як аргументи функції: `apply` - Передаєш метод як колбек і хочеш зберегти `this`: `bind` - Фіксуєш частину аргументів для пізніших викликів: `bind` - Виправляєш втрату `this` у `setTimeout`, обробниках подій або React-методах: `bind` ### Таблиця порівняння | | `call` | `apply` | `bind` | |---|---|---|---| | Виконується одразу | Так | Так | Ні | | Повертає нову функцію | Ні | Ні | Так | | Формат аргументів | Окремі значення | Масив | Окремі значення (зафіксовані) | | Часткове застосування | Ні | Ні | Так | | Працює з `new` | Так | Так | Ні (TypeError) | | Типовий сценарій | Позичити метод | Передати масив як аргументи | Колбек з фіксованим контекстом | ### Як це працює всередині Коли ти викликаєш `fn.call(obj, arg)`, рушій встановлює `[[ThisValue]]` у поточному кадрі виклику на `obj` до запуску `fn`. `apply` робить те саме, але спочатку розпаковує масив у позиційні аргументи. `bind` створює новий екзотичний об'єкт `BoundFunction` із зафіксованим слотом `[[BoundThis]]` і масивом `[[BoundArgs]]`. При наступному виклику зв'язаної функції рушій підставляє зафіксовані аргументи, відновлює `this` і делегує виконання оригінальній функції. Зв'язані функції не мають поведінки `[[Construct]]`, тому передача такої функції в `new` кидає `TypeError`. ### Типові помилки **1. Очікування що `bind` виконає функцію одразу** ```javascript const obj = { x: 10 }; function log() { console.log(this.x); } log.bind(obj); // нічого не відбувається - повертає функцію, не викликає її ``` `bind` повертає нову функцію. Виклич її окремо: `const bound = log.bind(obj); bound();` **2. Передача не-масиву в `apply`** ```javascript function add(a, b) { return a + b; } add.apply(null, 1, 2); // TypeError: CreateListFromArrayLike called on non-object ``` `apply` очікує масив другим аргументом. Виправлення: `add.apply(null, [1, 2])` або `add.call(null, 1, 2)`. **3. Ланцюжок `bind` з очікуванням нового контексту** ```javascript function show() { console.log(this.name); } const a = show.bind({ name: 'Alice' }); const b = a.bind({ name: 'Bob' }); b(); // "Alice" - перший bind перемагає завжди ``` Після того як `this` зафіксований першим `bind`, жоден наступний це не змінить. Слот `[[BoundThis]]` встановлюється один раз і залишається. **4. Використання зв'язаної функції з `new`** ```javascript function Counter(start) { this.count = start; } const BoundCounter = Counter.bind(null, 10); new BoundCounter(); // TypeError ``` Зв'язані функції не підтримують `[[Construct]]`. Якщо потрібне часткове застосування з конструктором, використовуй фабричну функцію. **5. `bind(null)` у strict mode** ```javascript 'use strict'; function fn() { console.log(this); } fn.bind(null)(); // null (не глобальний об'єкт) ``` У звичайному режимі `null` перетворюється на глобальний об'єкт. У strict mode залишається `null`. Знай, в якому режимі виконується твій код. Цей патерн bind-у-конструкторі в React-компонентах я бачив у десятках кодових баз. Щойно хтось забуває його і отримує `Cannot read property of undefined` під час події, одразу стає зрозуміло навіщо `bind` взагалі існує. ### Де зустрічається в реальних проектах - React класові компоненти: `this.handleClick = this.handleClick.bind(this)` у конструкторі, щоб зберегти `this` при передачі методу як пропс - Express: `app.use(handler.bind(dbContext))` для передачі з'єднання з базою даних у кожен обробник маршруту - DOM-події: `element.addEventListener('click', handler.bind(component))`, щоб `this` залишався компонентом, а не DOM-елементом - Часткове застосування: `const double = multiply.bind(null, 2)` для створення спеціалізованих функцій із загальних - `Math.max.apply(null, числовийМасив)` - класичний паттерн до появи spread, ще зустрічається у старих кодових базах ### Питання на співбесіді **Q:** Що станеться якщо передати примітив як `thisArg` у `call`? **A:** У звичайному режимі примітив автоматично загортається в об'єкт-обгортку (наприклад, `5` стає `Number {5}`). У strict mode залишається примітивом. Уникай цього і передавай об'єкти. **Q:** Чи можна перевизначити `this` через ланцюжок `bind` і `call`? **A:** Ні. `fn.bind(obj).call(otherObj)` все одно використає `obj` як `this`. Перший `bind` перемагає. Зафіксований `this` не можна перевизначити ні через `call`, ні через ще один `bind`. **Q:** Як написати поліфіл для `bind`? **A:** Зафіксуй оригінальну функцію і `boundThis` у замиканні (closure), поверни нову функцію що викликає оригінал через `.apply(boundThis, boundArgs.concat(newArgs))`. Повноцінний поліфіл також перевіряє `instanceof`, щоб кинути помилку при спробі використати `new`. **Q:** Чому `call`, `apply` і `bind` не впливають на `this` у стрілкових функціях? **A:** Стрілкові функції захоплюють `this` лексично в момент свого визначення. У них немає власного `[[ThisValue]]`. Будь-який `thisArg` ігнорується для `this`, але аргументи передаються нормально. **Q:** Чи може `apply` замінити spread-оператор при передачі масивів? **A:** Так, `fn.apply(null, arr)` і `fn(...arr)` функціонально еквівалентні. У сучасному коді spread зручніший, але `apply` ще зустрічається в старих кодових базах і в завданнях на написання поліфілів. ## Приклади ### Базовий: позичання методу між об'єктами ```javascript const dog = { sound: 'woof' }; const cat = { sound: 'meow' }; function makeNoise(times) { for (let i = 0; i < times; i++) { console.log(this.sound); } } makeNoise.call(dog, 2); // "woof" "woof" makeNoise.call(cat, 1); // "meow" ``` Одна функція, два різні об'єкти як `this`. Без дублювання коду. Це і є основна причина існування `call`. ### Середній: bind у React класовому компоненті ```javascript class Button extends React.Component { constructor(props) { super(props); // прив'язуємо тут, щоб this.props працював при спрацюванні DOM-події this.handleClick = this.handleClick.bind(this); } handleClick() { console.log('Клікнуто:', this.props.label); } render() { return <button onClick={this.handleClick}>{this.props.label}</button>; } } ``` Без `bind` передача `this.handleClick` як пропс відриває метод від екземпляра. Коли спрацює DOM-подія, `this` буде `undefined` у strict mode. `bind` у конструкторі фіксує контекст один раз і він зберігається для всіх рендерів. ### Просунутий: часткове застосування через bind ```javascript function fetchData(baseURL, endpoint, id) { console.log(`GET ${baseURL}${endpoint}/${id}`); } // фіксуємо базовий URL для всіх API-запитів const apiCall = fetchData.bind(null, 'https://api.example.com'); // фіксуємо endpoint для запитів користувачів const getUser = apiCall.bind(null, '/users'); getUser(42); // "GET https://api.example.com/users/42" getUser(99); // "GET https://api.example.com/users/99" ``` Кожен `bind` фіксує ще більше аргументів. `this` тут `null`, бо `fetchData` його не використовує. У підсумку маємо ланцюжок спеціалізованих функцій із однієї загальної — без жодного зайвого коду-обгортки.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.