Чем отличаются bind, apply и call?

MiddleJavaScript #javascript #this #bind-apply-call

Вопрос

Чем отличаются методы bind, apply и call, и когда какой использовать?

Короткий ответ

Все три метода позволяют явно задать значение this при вызове функции, но по-разному: call и apply вызывают функцию немедленно (различаясь только форматом передачи аргументов — через запятую или массивом), а bind не вызывает функцию, а возвращает новую функцию с навсегда привязанным this.

Подробный ответ

  • call(thisArg, arg1, arg2, ...) — вызывает функцию сразу, с указанным this и аргументами, перечисленными по одному.
  • apply(thisArg, [arg1, arg2, ...]) — делает то же самое, что и call, но аргументы передаются единым массивом. Удобно, когда аргументы уже собраны в массив или их количество заранее неизвестно.
  • bind(thisArg, arg1, ...) — не вызывает функцию. Вместо этого возвращает новую функцию, у которой this (и, опционально, часть аргументов — частичное применение) зафиксированы навсегда и не могут быть переопределены даже повторным call/apply поверх неё.

Практическое правило: call/apply — для одноразового вызова с нужным контекстом прямо сейчас; bind — когда функцию нужно передать куда-то на будущее (обработчик события, колбэк), но с гарантированно правильным this.

Важный нюанс: если передать bind/call/apply стрелочной функции — они не окажут никакого эффекта на this, потому что у стрелочных функций нет собственного this, который можно было бы переопределить (см. вопрос про this и стрелочные функции).

Пример

const user = {
  name: 'Alice',
  greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`)
  }
}

const otherUser = { name: 'Bob' }

// call — аргументы через запятую, вызов немедленный
user.greet.call(otherUser, 'Привет', '!') // "Привет, Bob!"

// apply — аргументы массивом, вызов немедленный
user.greet.apply(otherUser, ['Привет', '!']) // "Привет, Bob!"

// bind — возвращает новую функцию, вызов откладывается
const greetBob = user.greet.bind(otherUser, 'Привет')
greetBob('!') // "Привет, Bob!" — вызывается позже, this уже зафиксирован

// Типичный случай использования bind — обработчик события
class Counter {
  count = 0
  increment() {
    this.count++
  }
}

const counter = new Counter()
// без bind при клике this внутри increment будет undefined/button
button.addEventListener('click', counter.increment.bind(counter))

Дополнительные вопросы

  • Почему повторный bind поверх уже привязанной функции не может изменить её this?
  • Как реализовать свой bind через замыкание?
  • Чем Function.prototype.apply удобен при вызове функции с переменным числом аргументов (например, Math.max(...array) vs Math.max.apply(null, array))?