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
.
И еще чужое видео на ту же тему, на случай если кто-то не понял моего объяснения: