
Applying The Call To Bind
As one progresses towards gaining an upper level proficiency in any tool or skill, there comes a time when they need to confront the more challenging aspects of that said skill. Javascript for anyone who's been around the language for a while has some of these rather unique attributes built around it that kind of puts it in it's own niche when compared to the more common concepts you typically encounter with traditional languages. It's what makes the language both a joy and sometimes a pain to deal with(if you're not really well versed in some of those concepts that is). In this particular case, I'm talking about the will to "apply a call to bind" or in other words the ABC methods of JS that you don't typically see or use on a daily basis. Perhaps they're abstracted by libraries or frameworks you use. At the core they're function based utilities that deal with the concept of invoking functions in the right context. And we know that JS supports 4 ways of invoking functions viz.
- Function Invocation: called directly from global scope
- Method Invocation: called as part of a method tied to an object
- Constructor Invocation: using the "new" keyword and prototypal inheritance properties
- Apply/Call/Bind Invocation: The content of this article
One of the really interesting features of JavaScript is that function context is defined while calling the function, not while defining it! It's a form of late binding typical to many interpreted langauges. However, this late binding is a powerful mechanism allowing re-use of loosely coupled functions in a variety of contexts thus extending the modularity of the langauge as a whole. Let's dive a little deeper into each of those 3 methods with valid use cases of when and when not to use them or if there's a prettier alternative in Modern JS aka ES6.
Call & Apply
The reason I club both these function utilities together is because their end effect is exactly the same. It's like if you know one, you know the other except for the minor nuances in how they're used at invocation time. With the help of a mnemonic, it'll be even easier to remember what does what. So what do they do? They are used to immediately invoke a function with a certain context. An example below will explain these things a little better.
class Avenger {
constructor({ name, power }) {
this.name = name;
this.power = power;
}
introduction(...args) {
console.log(
`I'm ${this.name} and I can ${this.power} with ${args.length} arguments`
);
}
}
const avengerInstance = new Avenger({
name: 'Iron Man',
power: 'shoot missiles off my suit',
});
avengerInstance.introduction(1, 2, 3);
// C for Call, C for "call" a function normally with arguments
avengerInstance.introduction.call(
{ name: 'The Hulk', power: 'smash things' },
1,
2
);
// A for Apply, A for "apply" an array of arguments
avengerInstance.introduction.apply({ name: 'The Hulk', power: 'smash things' }, [
1,
2,
]);
/* Output:
I'm Iron Man and I can shoot missiles off my suit with 3 arguments
I'm The Hulk and I can smash things with 2 arguments
*/
So essentially, I'm able to call a method of an object with a similarly defined object. This kind of code promotes a high level of usability where I can have a same set of object operators that can operate on different instances of objects that share a few common traits. Let's look at another example to drive the point home further.
const name = 'Working Class Hero';
const avenger1 = { name: 'Iron Man', color: 'red and yellow' };
const avenger2 = { name: 'The Hulk', color: 'green' };
function introduce() {
console.log(`Hello, I'm ${this.name}`);
}
introduce();
introduce.call(avenger1);
introduce.apply(avenger2);
/* Output:
* Hello, I'm Working Class Hero
* Hello, I'm Iron Man
* Hello, I'm The Hulk
*/
Notice the context in which the introduce method is called each time and the resulting value of this. Let's now take a look at the more interesting bind function.
Bind
I think one of the more easier ways to remember what bind is or does is to byheart this saying to some extent: Bind Once. Call Later!. Bind is typically useful in cases where you'd like a function to be invoked with a certain context at a later time but want to set it's context correctly beforehand. Think browser events for a common use case. It's also useful when you want to pass the this in class methods to external functions. If you've done React from it's earlier days, there was a common practice to bind class methods in the constructor until of course it became more convenient to use arrow functions. More on that later. So let's look at some code to understand this method a little better.
const name = 'Working Class Hero';
const avenger1 = { name: 'Iron Man', color: 'red and yellow' };
const avenger2 = { name: 'The Hulk', color: 'green' };
function introduce() {
console.log(`Hello, I'm ${this.name}`);
}
const bound0 = introduce.bind({ name });
bound0();
const bound1 = introduce.bind(avenger1);
bound1();
const bound2 = introduce.bind(avenger2);
bound2();
/* Output:
* Hello, I'm Working Class Hero
* Hello, I'm Iron Man
* Hello, I'm The Hulk
*/
One of the important things to take note of with bind is that once the function is bound to a given variable, it cannot be changed. In other words, the chosen value of this is immutable. This is important to take note of lest you want to rebind the same bound function down the line. As usual, an example will make it clearer.
const avenger1 = { name: 'Iron Man', color: 'red and yellow' };
const avenger2 = { name: 'The Hulk', color: 'green' };
function introduce() {
console.log(`Hello, I'm ${this.name}`);
}
const bound1 = introduce.bind(avenger1);
bound1();
// these bind, call or applies below will have no effect on bound1's "this"
const bound2 = bound1.bind(avenger2);
bound2();
bound1.call(avenger2);
bound1.apply(avenger2);
/* Output:
* Hello, I'm Iron Man
* Hello, I'm Iron Man
* Hello, I'm Iron Man
* Hello, I'm Iron Man
*/
Now an interesting use case of not having to use bind is with the introduction of the new ES6 arrow functions. Call it syntactic sugar or whatever but boy do they make code more readable. In the example below, I've totally circumvented the use of bind by using arrow functions which leads to less verbose code and indirection. This is a popular pattern that most React components use these days for defining class methods.
class Avenger {
constructor({ name, power }) {
this.name = name;
this.power = power;
}
introduction(...args) {
console.log(
`I'm ${this.name} and I can ${this.power} with ${args.length} arguments`
);
}
delayedIntro1() {
const intro = this.introduction.bind(this);
const timeoutID = window.setTimeout(function() {
intro();
}, 1000);
}
delayedIntro2() {
const timeoutID = window.setTimeout(() => {
this.introduction();
}, 1000);
}
}
const avengerInstance = new Avenger({
name: 'Iron Man',
power: 'shoot missiles off my suit',
});
avengerInstance.delayedIntro1();
avengerInstance.delayedIntro2();
Of course bind has been natively defined in almost all browsers and we thankfully don't have to support IE much longer these days. But just for the record, bind was introduced natively to browsers after call and apply chronologically. So in case, you ever needed to implement bind via a call/apply proxy, the example below is a rough implementation. Please do not use this as a reference implementation; it's just there to illustrate and bring everything we've talked about so far into one concise bit of code. This includes the use of apply, bind and arrow functions.
Function.prototype.bind =
// always check to see if it's natively defined as a good practice
Function.prototype.bind ||
function(context) {
return () => {
return this.apply(context, arguments);
};
};
const avenger1 = { name: 'Iron Man', color: 'red and yellow' };
const avenger2 = { name: 'The Hulk', color: 'green' };
function introduce() {
console.log(`Hello, I'm ${this.name}`);
}
const bound1 = introduce.bind(avenger1);
bound1();
const bound2 = introduce.bind(avenger2);
bound2();
/* Output:
* Hello, I'm Iron Man
* Hello, I'm The Hulk
*/
Conclusion
Let sum up everything we've spoken about the ABC methods. Use bind when you want a function to later be called with a certain context. Use call or apply when you want to invoke the function immediately, and modify the context.
And of course you'll find ton of examples on the internet with more details if you're looking for a deeper dive.