bind
is a common JS keyword. If you read enough JS code, you probably have seen it (this.something = this.something.bind(this)
in React, anyone?). Whenever I see bind
, I would get really confused. If this is you, by the end of this article you are bound (see what I did there 😎?) to understand bind
much better!
Warning: knowledge of this
is a prerequisite to understand bind. I wrote an article about this
, check it out!
Bind has many applications and it would be impossible to cover in a short article like this, but I think the essentials are:
bind
in a regular functionbind
's 2nd argumentbind
in arrow functionbind
in callback function
So what is bind?
JS bind
"binds" a function's this
method with your value. It can also "bind" arguments.
Bind in a regular function
What does it mean to bind a function's this
method?
Recall that this
, when called from inside a function, refer to an object where that function is called from.
const obj = {
breakfast: 'pancakes',
sayBreakfast(){
console.log(`I had ${this.breakfast} for breakfast`)
}
}
obj.sayBreakfast() // pancakes
With bind
, we can bind sayBreakfast's this
value to anything we want.
const waffles = obj.sayBreakfast.bind({breakfast: "waffles"})
waffles() // waffles
obj.sayBreakfast() // pancakes
Bind's 2nd argument
Let's use an example of a regular function, inside an object, that takes an argument:
const food = {
entree: 'fried rice',
sayLunch(appetizer) {
console.log(`I had ${appetizer} and ${this.entree} for lunch`)
}
}
food.sayLunch('dumplings') // dumplings and fried rice
Let's bind it with sushi:
const lunchie = food.sayLunch.bind({entree: 'sushi'})
lunchie() // undefined and sushi
Woops, we still need to pass an argument - otherwise it returns undefined
, so let's do that:
lunchie('miso soup') // miso soup and sushi
Bind's 2nd argument can "lock" argument(s) values - giving it pre-specified values.
const lunchy = food.sayLunch.bind({entree: 'tacos'}, 'chips')
lunchy() // chips and tacos
lunchy('burritos') // still chips and tacos
If you want to give a function pre-specified values but don't want to bind anything, put null
as first argument.
const addStuff = function(first, second) {
return first + second
}
addStuff(10,5) // 15
const addHundred = addStuff.bind(null, 100) // first is now 100
addHundred(3) // 103
addHundred(1,5) // 101
We bound the first
argument to 100. Once bound, first
will always be 100. Hence addHundred(1,5)
is returning 101 and not 6. 1
becomes second
argument and 5
is technically the third argument now.
Bind in arrow function
This section assumes a lot of this
knowledge.
From my previous article, I mentioned:
- Only regular function and global function can have
this
. - Arrow function does not have
this
on its own - When
this
is referred to inside an arrow function, it will look up the scope to find this value. It behaves like lexical scope.
We will keep these in mind as we go through the last 2 sections.
Let's start by binding {a: "rock"}
into sayThis
arrow function.
const sayThis = () => console.log(this);
sayThis() // window obj
const imnotarock = sayThis.bind({a: "rock"})
imnotarock() // still window
It still returns window because arrow function does not have its own this
. It looks up lexically for either regular function or global object's this
.
This would have worked fine on regular function:
const sayThisAgain = function(){console.log(this)}
const imarock = sayThisAgain.bind({a: "rock"})
imarock() // {a: "rock"}
Although we can't bind this
, we can give an arrow function pre-specified values.
const addFive = (x) => x + 5
addFive(10) // 15
const gimmeSeven = addFive.bind(null, 2)
gimmeSeven() // 7
gimmeSeven(10) // still 7
Bind in callback function
Let's say we have a sayDinner()
regular function, inside dinner object, using reduce()
:
let dinner = {
meals: ['pizza', 'pie', 'tea'],
sayDinner() {
let food = this.meals.reduce(function(acc, currentEl){
if(currentEl === this.meals.slice(-1)[0]){
return `${acc} ${currentEl}!`
}
return `${acc} ${currentEl},`
}.bind(this), "")
console.log(food)
}
}
dinner.sayDinner() // pizza, pie, tea!
(Btw, if you are not familiar with reduce, check this out)
Pay attention at .bind(this)
at the end of our reducer function. The bind is necessary to give this.meals
context.
Let me explain:
When the callback function is called, it has no idea what this.meals
(the one inside reducer function that is being sliced) is. It does not even know that dinner
object exists. It only knows acc
and currentEl
. When we do .bind(this)
, we are telling the reducer, "Hey, if you see this.meal
inside yourself, you can use dinner
's meals
."
Try the above again without .bind(this)
dinner = {
meals: ['pizza', 'pie', 'tea'],
sayDinner() {
let food = this.meals.reduce(function(acc, currentEl){
if(currentEl === this.meals.slice(-1)[0]){ // does not know about this.meals if we don't bind it
return `${acc} ${currentEl}!`
}
return `${acc} ${currentEl},`
}, "")
console.log(food)
}
}
dinner.sayDinner() // error
This gives an error "Cannot read property 'slice' of undefined"
because this.meals
inside our callback function is undefined if not bound.
Retrospectively, let's replace our callback function from regular function into arrow function. It works perfectly without bind
:
dinner = {
meals: ['pizza', 'pie', 'tea'],
sayDinner() {
let food = this.meals.reduce((acc, currentEl) => {
if(currentEl === this.meals.slice(-1)[0]){
return `${acc} ${currentEl}!`
}
return `${acc} ${currentEl},`
}, "")
console.log(food)
}
}
dinner.sayDinner() // pizza, pie, tea!
Recall three things about this
and arrow function mentioned above.
In this case, our reducer arrow function, seeing this.meals
inside itself and does not know what it means, lexically looks up at sayDinner()
function. Since sayDinner()
is a regular function, it does have this.meals
context.
What if we change sayDinner()
from regular to arrow function?
Something like this:
dinner = {
meals: ['pizza', 'pie', 'tea'],
sayDinner: () => {
let food = this.meals.reduce((acc, currentEl) => {
if(currentEl === this.meals.slice(-1)[0]){
return `${acc} ${currentEl}!`
}
return `${acc} ${currentEl},`
}, "")
console.log(food)
}
}
Let's think: we are inside reducer function trying to get this.meals
. Recall our three rules above, rule #2: arrow function does not have this
on its own, so it will look for regular function or object that has this.meals
. The next object it checks is global window object, but it finds none. It throws an error: "Uncaught TypeError: Cannot read property 'reduce' of undefined"
Of course, we can always define a global meals
, like
window.meals = ['Hamburger', 'Fries']
const dinner = {...}
dinner.sayDinner() // hamburger, fries
And it would have worked just fine :)
There you have it folks! Javascript bind
. Now go bind stuff!!!