call, apply, bind #
call, apply и bind — методы функций для явного выбора значения this и управления аргументами. Они не меняют определение функции, а задают контекст на момент вызова или возвращают новую функцию с «приклеенным» this.
Зачем это нужно #
this в обычной функции зависит от способа вызова, а не от места объявления:
const counter = {
value: 0,
increment() {
this.value += 1
return this.value
},
}
const fn = counter.increment
fn() // NaN или ошибка в strict — this не counter
Передача метода как колбэка «отрывает» объект от this. call/apply/bind возвращают контроль.
call #
Вызывает функцию с заданным this и аргументами по одному:
function formatProgress(done, total) {
return `${this.label}: ${done}/${total}`
}
const course = { label: 'JSFlow' }
formatProgress.call(course, 3, 10) // "JSFlow: 3/10"
Первый аргумент после this — первый параметр функции, далее по порядку.
apply #
То же, что call, но аргументы передаются массивом (или array-like):
const stats = [7, 12, 4]
Math.max.apply(null, stats) // 12
// в современном коде чаще: Math.max(...stats)
apply полезен, когда число аргументов заранее неизвестно и массив уже есть. Для фиксированного списка предпочтительны spread и call.
formatProgress.apply(course, [5, 10]) // "JSFlow: 5/10"
bind #
Не вызывает функцию сразу — возвращает новую с навсегда привязанным this (и опционально частью аргументов):
const boundIncrement = counter.increment.bind(counter)
boundIncrement() // 1
boundIncrement() // 2
Привязка для колбэков:
const ui = {
title: 'Урок',
render() {
console.log(`Рендер: ${this.title}`)
},
}
setTimeout(ui.render.bind(ui), 100)
Частичное применение #
Второй и следующие аргументы bind фиксируются при создании:
function createUrl(base, path, query) {
return query ? `${base}${path}?${query}` : `${base}${path}`
}
const docsUrl = createUrl.bind(null, 'https://jsflow.dev', '/docs')
docsUrl('functions') // "https://jsflow.dev/docs?functions"
docsUrl('oop', 'page=1') // "https://jsflow.dev/docs?oop?page=1" — query передан при вызове
null или undefined как this в нестрогом режиме подменяется глобальным объектом; в функциях без использования this это обычная практика.
Сравнение #
| Метод | Вызов сразу | this | Аргументы |
|---|---|---|---|
call | да | первый параметр | по одному |
apply | да | первый параметр | массив |
bind | нет (новая функция) | первый параметр | можно зафиксировать часть |
Заимствование методов #
Любую функцию можно вызвать на «чужом» подходящем объекте:
const items = ['closures', 'spread', 'bind']
Array.prototype.join.call(items, ' | ')
// "closures | spread | bind"
Так поступают с array-like объектами (например, arguments в старом коде) до появления rest-параметров.
bind и стрелочные функции #
Стрелки не имеют собственного this — call/apply/bind не переназначают контекст, только передают аргументы:
const arrow = (x) => x * 2
arrow.call({ value: 10 }, 5) // 10 — this игнорируется
Для методов, где важен динамический this, используйте обычные function.
Частые ошибки #
bindв цикле без необходимости — создаётся новая функция на каждой итерации; иногда достаточно стрелки с лексическимthis.- Двойной bind — повторная привязка не переопределяет первую (жёстко зафиксированный
this). - Потеря
thisв обработчиках —button.addEventListener('click', obj.handle)ломает контекст; нуженbindили стрелка-обёртка. - Путаница call и apply — перепутали порядок: в
applyвторой аргумент обязан быть массивом.
// ошибка: второй аргумент не массив
// formatProgress.apply(course, 3, 10)
formatProgress.apply(course, [3, 10]) // верно
Итог #
callиapplyнемедленно вызывают функцию с выбраннымthis; разница только в форме аргументов.bindсоздаёт новую функцию с фиксированнымthisи опционально частью параметров.- Три метода решают проблему «оторванного» метода в колбэках и таймерах.
- Стрелочные функции не переназначают
thisчерез эти методы.