Clarifying This in JavaScript
In JavaScript, this
is an integral concept that is easily stumbled over. this
refers to the the execution context of a function, which gets established at execution time, not definition time.
It’s important to understand the difference, because JavaScript has first-class functions — which means that functions are treated like regular objects in the sense that they can be stored in variables and passed around to other functions.
Functions that take other functions either as arguments, return a function or both are known as higher order functions.
So why do we care that this
gets established at execution time? Because if we take a function and pass it around without being careful, we will experience context loss because the value of this
will change based on where that function finally ends up being invoked. Clear as mud, right? Let’s look at an example:
On line 3 we assign an anonymous function to the property getBar
on the object foo
. When we invoke getBar
in this context on line 8, getBar
is a method of the object foo
, so the implicit value of this
on line 4 is the parent object foo
, and the code produces an unsurprising result: logging the value of foo.bar
which is baz
.
Now for the trickiness: line 10 assigns the value of foo.bar
to the variable qux
in the global context, and does so without invoking the anonymous function stored within foo.bar
. When we do invoke the function stored in qux
on line 11, the execution context is now different than it was before, so the implicit execution context here is the global object. In other words, this
now refers to global
or window
(depending on whether this JavaScript is running in node or in a browser — from here on I will just say global
).
So why does line 11 log undefined
? Because we experienced context loss! We tried to log the value of this.bar
, except that in this execution context this.bar
resolves to global.bar
. Because there is currently no bar
property on the global
object, the returned value is undefined
.
So how to we fix this?
Instead of relying on the implicit execution context, we can decide for ourselves what the execution context should be (i.e. value of this
within the invoked function). We call that explicit execution context. There are several ways we can achieve that.
Here, at invocation time we utilize the the Function.prototype
methods call
and apply
to explicitly set this
to the object foo
when the function executes. It’s a trivial example, but worth noting that we could pass any context here, not just foo
. While call
and apply
may look to be aliases for one another, they are not. Both methods take the explicit context to be set for their first argument. The difference is that apply
expects its next argument to be an array containing all the other arguments to pass on to the function, while call
expects each argument to be passed individually.
Let’s examine those differences and their limitations before we look at a more permanent solution.
Here we can see that apply
expects an array of arguments that it will pass on to the function that is having its context set, while call
needs each argument to be passed separately. Line 12 logs NaN
(or ‘Not a Number’) because 5 * undefined === NaN
. It’s worth noting that when we use both call
and apply
, we are setting the context of this
only for that specific function execution. For a more permanent solution we need to use bind
.
By permanently binding the context of qux
on line 10, each invocation of qux
will have its context bound to foo
and will no longer experience context loss. Now take a look at line 6. By passing a second argument to forEach
we can utilize the optional thisArg
argument to similarly set the context of the callback function passed to forEach
. It’s a clunky technique, but I’ll show a better way shortly. In the above example, because the callback function passed to forEach
on line 4 is not a method on the foo
object, it will implicitly execute as a function, not a method. By executing as a function, its implicit value of this
would resolve to the global
object, except that we corrected for that by passing in the thisArg
.
This kind of messiness is why I prefer to use ES6 arrow functions to achieve the same thing with a much cleaner syntax. ES6 arrow functions do not change the context of this
. Also note that as a one-liner, the braces can be omitted. The parenthesis can also be omitted because there is only a single function parameter.
Keep in mind when using bind
that it will only work with a function expression (i.e. a function stored in a variable). It will not work with a function declaration. There are other ways of passing the context of this
into functions nested in methods, such as saving the value of this
into a local variable called something like self
, but that approach is messy and made irrelevant by ES6 arrow functions.
Summary:
It’s imperative to remember that function execution context a.k.a this
is implicitly determined at invocation time, unless it gets explicitly set beforehand. Function execution context (this
) can be explicitly set for a specific invocation with call
or apply
, or permanently bound by using the bind
function.
For further reading, check out the MDN docs: call, apply and bind.