Controlling Javascript Context

Exploring call, apply and bind in JS.

When entering the world of Javascript, the flexibility first experienced can feel empowering, even intoxicating, especially when migrating from other programming languages. You’ve heard that the language has its quirks yet you aren’t seeing what all the complaining has been about, until the inevitable..

“What the fuck!?!?”
- Every new Javascript developer ever.

Fear not fellow developers, Javascript offers you a few native methods for taking scope into your own hands, allowing you to keep control and your sanity.

  • Function.prototype.call()
  • Function.prototype.apply()
  • Function.prototype.bind()

Crack open a cold one and we’re going to walk through this step-by-step and explain with examples when to use each prototype method.

— CALL —

When wanting to execute a function that references a this context inside of the function body, we can use call() to pass the desired object scope as the first parameter to the call() method, followed by a COMMA SEPARATED list of arguments if we have more than one parameter that we want to populate.

Let’s get started by creating two different animal objects as well as a function that returns the type of animal that we are scoping, followed by the sound we want it to make, which we will pass as the second argument to call() after we establish our variables and function.

var dog = { type: 'dog' };
var cat = { type: 'cat' };
function animalSound (sound) {
return "The " + this.type + " makes the sound " + sound;
}

Now we have a dog and a cat object, containing their corresponding animal types as a property value, as well as a function that we can pass a ‘sound’ argument to. Let’s now utilize the call() method to control the animal scope and immediately call the function with our furry friends desired sound.

// Call the dog object and pass 'WOOF' to the sound parameter
animalSound.call(dog, 'WOOF');
// The function returns:
"The dog makes the sound WOOF"

Next we will take the same approach, except with a cat this time!

// Call the cat object and pass 'MEOW' to the sound parameter
animalSound.call(cat, 'MEOW');
// The function returns:
"The cat makes the sound MEOW"

As you can see, although we are utilizing the exact same function call animalSound(), we can still control the way it executes the scope context by passing the animal object we want to use with the function.

Since animalSound() returns this.type within the function body, we have bound the object scope to the function using call(), allowing us to have this.type point to the cat’s type property or the dog’s type property.

— APPLY —

The Function.prototype.apply() method works nearly identically to the Function.prototype.call() method covered above. The slight difference between the two methods are that call() takes a single or list of arguments (after declaring the object scope) separated by commas, whereas apply() receives a single argument in the format of an array. (Again, after declaring the object scope first).

TIP: I distinguish the two by remembering that (A)pply takes an (A)rray.

For the sake of brevity, I will use the same example above to demonstrate how to use apply() instead and only with the dog object.

  • call() and apply() example with a single argument list & array parameter
// call()
animalSound.call(dog, 'WOOF');
// apply()
animalSound.apply(dog, ['WOOF']);
  • call() and apply() example with multiple argument list/array items
/* NOTE: This assumes animalSound receives more than one param */
// call()
animalSound.call(dog, 'WOOF', 'GRRR');
// apply()
animalSound.apply(dog, ['WOOF', 'GRRR']);

Hopefully you now have a further understanding of the differences between call() and apply(), as well as the purpose behind the methods and how to use them to control your scope context. Let’s finish up by moving onto Function.prototype.bind() before calling() it a day (See what I did there?).

— BIND —

Last but definitely not least, we have the sexy bind() method. What really stands out when examining bind() in comparison to call()/apply() is the time of function execution as well as the fact that bind returns a newly created function that retains the scope bound to it.

When we execute our trusty animalSound() function with either .call() or .apply(), we are immediately invoking the function with the given scope. Let’s try saving animalSound() with a dog object to a variable and see what happens.

var callDog = animalSound.call(dog, 'WOOF');
// Executed immediately, even when storing the call to a variable
"The dog makes the sound WOOF"

Okay, so that is a good start! NOW lets try executing our callDog variable as a function and see what is returned, it should be “The dog makes the sound WOOF” right??

Wrong.

callDog();
Uncaught TypeError: callDog is not a function(…)

It seems as if storing our function call to a variable doesn’t work to our surprise. This is because the function is immediately executed and only once. You cannot store a reference to the called function and then invoke it by variable at a later time, and this my friends is where Function.prototype.bind() comes in to play.

Let’s now bind our animalSound() function to our cat AND dog objects and store them as separate variables, then see if we can invoke these variables without getting an error, as what happened above with call().

var catObj = animalSound.bind(cat, 'MEOW');
var dogObj = animalSound.bind(dog, 'WOOF');

When declaring these variables, nothing was executed in the console. Now let’s try to call the new variables and hope for the best...

catObj();
The cat sounds like: MEOW
dogObj();
The dog sounds like: WOOF

BOOM!

Just like that we have bound our animal objects to the animalSound() method without errors, with the desired scope AND are able to execute these scoped functions anytime in the future smoothly. A good way to think of bind() is to “save” the scoped method for later use, and to maintain and control our scope.


I’ve just begun my journey into Javascript technical blogging and hope that you enjoyed this article and found it helpful. Javascript scope has always been and remains to be an awkward concept, especially for new developers or developers coming from other and/or more classical OOP languages such as Java or C#.

Until next time.