Hello! Have you ever wondered about the mysterious functions bind, call, apply and their magical inner workings! Well it turns out you're in luck! Over the course of this blog I will do my best to remedy any confusion you may have about these somewhat confusing constructs.
In order to understand how these notorious functions work, a little background into the behavior of the keyword 'this' is necessary. According to the famous MDN docs, 'this' is "A property of an execution context (global, function or eval) that, in non–strict mode, is always a reference to an object and in strict mode can be any value." The object that 'this' is referencing is different in different situations. When using the functions .call(), .bind(), or .apply() is one of those situations. When you use any of these functions you can explicitly tell the interpreter what you want the value of 'this' to be by supplying that value as the first argument. This is where these functions really shine!(no pun intended, hehe 😜)
First up to bat is the function .call(). I chose to write about this one first because .bind() is implemented under the hood with .apply() and .apply() just has a slight variation from .call(). MDN tells us that "call() allows for a function/method belonging to one object to be assigned and called for a different object." This means that the function .call() allows you to take a method found on one object or a function and 'call' it with a different context. This is done via the first parameter supplied to the function. Pretty cool huh! Lets look at an example:
Here I constructed a georgeClinton object with two properties, profession and goal. Then a funkItUp function was created which logs to the console the string value of "this.profession" concatenated with "will" and the string value of "this.goal". Then I called the function with .call() specifying my value for 'this' as the georgeClinton object. Therefore the string of "the funk master will funkify the world" is logged to the console. When the interpreter reaches either of the statements containing the 'this' keyword, it looks to the georgeClinton object to find the values. Now lets see what would happen if I called this function without using .call().
When we call the function this way without using .call(), the interpreter uses the global Window as the value of 'this' and since there is no profession property or goal property on the Window it returns undefined. Therefore the string of "undefined will undefined" is returned. Lets see what happens when we supply a couple different objects as our first argument to .call().
In this example I created two other objects with the same key names as the original georgeClinton object but with different values. Here I was able to reuse the funkItUp function with different context values depending on what I supplied as my first argument to .call() therefore creating different logs to the console. When I supplied the jamesBrown object as my argument to .call(), "the godfather of soul will drop the Funk Bomb" was logged because those are the corresponding values associated with that particular object. The same was true when I used bootsyCollins, "the funkiest bass player will become the funkiest of the funky" was logged. The .call() function allows us to call a function with whatever context we need to use. We could have made a method on each object that does this same action but then we would be violating the golden rule of DRY (don't repeat yourself) plus creating more work for ourselves, 👀 BOOOO!
There is another aspect of .call() that I need to mention. This function can accept additional parameters after the first 'this' parameter is supplied which will act the same way the original function's parameters would act and if you do not need to supply a special value for 'this' when using .call(), null can be supplied for that first argument. When null is supplied as the first argument, the interpreter will look into the global scope to find the value of 'this'. The .call() function accepts it's additional parameters individually as opposed to a collection. Here is an example of using multiple parameters:
In this example I used the original georgeClinton object but created a new function called weWantTheFunk. The function takes two parameters, desire and action. Then I called weWantTheFunk with .call() supplying the georgeClinton object for the argument value of 'this' plus the strings of "wants to" and "bring the funk" as arguments for the desire and action parameters. Now when this function call happens the interpreter looks to the georgeClinton object to find the value of 'this' and uses the supplied arguments to find the other values, thus logging to the console "the funk master wants to bring the funk".
Next up to bat is .call()'s closest relative .apply()! This function is exactly like .call() except for one difference. The .apply() function takes an array or according to MDN "an array-like object" of arguments as it's second parameter. Instead of having to list your parameters individually, as with .call(), you must supply an array as the second argument. This is helpful when using the 'arguments' object or spread syntax. Lets take a look:
Last but not least, hitting cleanup here is the big-boy .bind()! This function is implemented with .apply() under the hood so it behaves very much the same way. The first argument we supply to .bind() will signify the object to be used for 'this'. It also takes multiple parameters. The main difference is that .bind() returns a new copy of the original function but with the new supplied context bound to it and if any arguments were supplied when the binding occurred than these arguments will always be supplied when the new bound function is called. Unlike .call() and .apply() which get invoked immediately, this new bound function can be used at any time. You can also supply additional arguments when you call this new bound function. Check it out:
Here I have created a whole new function and anytime I call it I do not need to specify the value for 'this'. That value will be forever bound to the georgeClinton object. As you can see, "the funk master will funkify the world" is logged to the console when I invoke it.
In conclusion, the functions .bind(), .call(), and .apply() are all very similar. Their main purpose is to give you the ability to invoke a function with a specific context. This is done by supplying that context as their first argument. The main differences are .call() and .apply() are immediately invoked but .bind() creates a whole new function that can be called at anytime. They can all take multiple arguments besides the first 'this' argument but .apply() must receive its second argument as an array. If the function .bind() is called with additional arguments at bind time then any time you call this new function, it will be called with those original arguments and any other new ones that were supplied at call time. I hope this clears up any confusion you may have about these tricky functions. Thanks for reading!