Skip to main content

Методи call, apply та bind у JavaScript

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

Таблиця порівняння

callapplybind
Виконується одразуТакТакНі
Повертає нову функціюНіНіТак
Формат аргументівОкремі значенняМасивОкремі значення (зафіксовані)
Часткове застосуванняНіНіТак
Працює з 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 його не використовує. У підсумку маємо ланцюжок спеціалізованих функцій із однієї загальної — без жодного зайвого коду-обгортки.

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

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

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

Дочитали статтю?