call, apply, bind

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 и стрелочные функции #

Стрелки не имеют собственного thiscall/apply/bind не переназначают контекст, только передают аргументы:

const arrow = (x) => x * 2
arrow.call({ value: 10 }, 5) // 10 — this игнорируется

Для методов, где важен динамический this, используйте обычные function.


Частые ошибки #

  1. bind в цикле без необходимости — создаётся новая функция на каждой итерации; иногда достаточно стрелки с лексическим this.
  2. Двойной bind — повторная привязка не переопределяет первую (жёстко зафиксированный this).
  3. Потеря this в обработчикахbutton.addEventListener('click', obj.handle) ломает контекст; нужен bind или стрелка-обёртка.
  4. Путаница call и apply — перепутали порядок: в apply второй аргумент обязан быть массивом.
// ошибка: второй аргумент не массив
// formatProgress.apply(course, 3, 10)

formatProgress.apply(course, [3, 10]) // верно

Итог #

  • call и apply немедленно вызывают функцию с выбранным this; разница только в форме аргументов.
  • bind создаёт новую функцию с фиксированным this и опционально частью параметров.
  • Три метода решают проблему «оторванного» метода в колбэках и таймерах.
  • Стрелочные функции не переназначают this через эти методы.

См. также #