JavaScript: bind против apply и call
Опубликовано by Pavel Nakonechnyy on (изменено: ) в Web development.В чем смысл использования bind, когда у нас уже есть apply и call? Да и что вообще такое bind и где его использовать? Это мы подробно разберем в нашей статье на примере следующего кода.
Для тех, кто не знаком с использованием .bind(), .apply() и .call(), приведу небольшое объяснение.
Все три функции являются методами (функциями, прикрепленными к объекту функции), которые вызывают функцию с кастомным this контекстом.
apply
showDetails.apply() обработает функцию showDetails с fruit в качестве this контекста. Параметры функции будут переданы в массиве следующим аргументом после контекста, как показано в примере ниже.
showDetails.apply(fruit, ['small', 1])
// -> Apple small: $1/kg
call
showDetails.call() вызовет функцию showDetails с fruit в качестве контекста, но параметры будут переданы через запятую после объекта-контекста.
showDetails.call(fruit, 'medium', 5)
// -> Apple medium: $5/kg
bind
bind довольно необычен: он вернет функцию, которая будет вызывать showDetails с fruit в качестве контекста.
var bound = showDetails.bind(fruit, 'large', 10)
bound()
// -> Apple large: $10/kg
var bound = showDetails.bind(fruit)
bound('large', 10)
// -> Apple large: $10/kg
Как вы могли заметить, .apply() и .call() вызываются сразу же, в то время как .bind() возвращает функцию, которая может быть вызвана позже. Но вы можете вызвать ее и сразу же
showDetails.bind(fruit, 'large', 10)()
// -> Apple large: $10/kg
Это возвращает нас к вопросу, зачем нам использовать bind, если мы могли ограничиться call или apply?
Несмотря на то, что bind похож на apply и call своей возможностью определять контекс функции, возможности bind гораздо шире:
bindпозволяет задавать аргументы функции при вызове.bind()или при выполнении функции с новым контекстом:
showDetails.bind(fruit, 'large', 10)()
// -> Apple large: $10/kg
var bound = showDetails.bind(fruit)
bound('medium', 7)
// -> Apple medium: $7/kg
Вы можете встретиться с ситуациями, когда у вас есть нужный контекст, но параметры станут известны только позднее (например, при использовании ООП). В таком случае bind — ваш незаменимый помощник.
bindсохраняет контекстthisдля будущего использования
Вам когда-либо приходилось применять var that = this или var self = this? При использовании bind вам это больше не потребуется.
Без bind:
var cat = {
name: 'Neo',
showName: function () {
var self = this
// здесь может быть любая функция, меняющая контекст: async/callback
setTimeout(function () {
console.log(self.name)
// `this.name` будет undefined
})
}
}
cat.showName()
// -> Neo
С bind:
var cat = {
name: 'Neo',
showName: function () {
var printer = function () {
console.log(this.name)
}.bind(this)
// прикрепляем текущий контекст `this` (ссылка на `cat`)
// к `printer` функции, пока у нас есть ссылка на контекст
setTimeout(printer)
}
}
cat.showName()
// -> Neo
Также контекст this сохраняется с использованием ES6 arrow functions.
bindможет сохранять текущие параметры для будущего использования
Вам когда-нибудь приходилось вызывать анонимную функцию, чтобы перевести параметры в функцию, которая иначе потеряла бы контекст? Сbindу вас есть более чистый способ решения таких ситуаций.
Например, к моменту когда код ниже выполняет console.log(pets[i]) i уже равно 3, а потому выведет undefined три раза, вместо ожидаемых имен питомцев.
var pets = ['Neo', 'Bud', 'Kalia']
for (var i = 0; i < pets.length; i++) {
// симуляция асинхронных операций
setTimeout(function () {
console.log(pets[i])
})
}
Проблема может быть решена созданием замыкания на каждой итерации и передаче текущего значения i ему, так чтобы каждый setTimeout получил своё изолированное значение индекса, не зависящее от финального значения i.
for (var i = 0; i < pets.length; i++) {
(function (i) {
setTimeout(function () {
console.log(pets[i])
})
})(i)
}
Несмотря на то, что данное решение работоспособно, оно не выглядит понятным и может стать источником серьезных утечек памяти.
Мы можем переписать код с использованием bind без необходимости создания дополнительного замыкания:
for (var i = 0; i < pets.length; i++) {
var printer = function (i) {
console.log(pets[i])
}.bind(undefined, i)
setTimeout(printer)
}
Таковы три хорошие причины зачем стоит использовать bind вместо apply или call.
Каждый раз, когда вы работаете с async/вложенным кодом и чувствуете, что вам нужно изощряться чтобы сохранить контекст, вероятно вам стоит использовать bind.
И еще чужое видео на ту же тему, на случай если кто-то не понял моего объяснения: