Losing the 'this' Binding in Inner Functions and How to Fix It
A common JavaScript interview question I have seen over the years is when an Interviewer looks to see if you have
a good understanding of the this
keyword, specifically dealing with inner nested functions. Many
newbie JavaScript Developers (experienced ones as well) struggle when it comes to understanding the this
keyword. They either get tripped up over the loss of the this
keyword for the very first time in an interview or draw a blank when a question like this is being asked.
In this article, I am going to show you a common set of JavaScript interview questions that test your knowledge and understanding of the this
keyword binding in inner functions.
I'll be going over what the actual problem is, why the this
keyword binding is lost in inner nested functions, and what are some possible solutions to solving the loss of the this
binding in inner functions. I will also show a real life example where this problem could potentially very well appear in a production code base.
The Problem
const car = {
brand: 'bmw',
getBrandName: function () {
let showBrand = function () {
console.log(this.brand)
}
showBrand()
}
}
car.getBrandName() // what is the output?
Can you guess what the output is? If you guessed undefined, then you are correct. But… why?
Why Do Inner Functions Lose Their 'this'?
Clearly the this.brand
in the showBrand()
function on line 5 is referring to the object
itself right? Not quite...
What we have here is a loss of implicit binding of the this
keyword in JavaScript.
Implicit Binding is when the
this
keyword refers to the object that calls that function. In our case thegetBrandName()
function call is called by the car object.
So the context in which the this
keyword refers to would be the car object itself.
A Global (Default) Binding is when a function is being called all on its own. So imagine callinggetBrandName()
as is, thethis
keyword would refer to the global scope.
We were unable to reference the car object because the inner function showBrand()
was referencing
the this
as
the global window object as opposed to the car object.
So in an interview, if someone asks you why would the output be undefined? Your answer should be:
Because
showBrand()
is an inner function ofgetBrandName()
and was called inside getBrandName, thethis
keyword gets lost and refers to the global window object rather than the car object which it was initially called from.
Of course a follow up would be, "How would you fix this issue?"
Solution(s) to 'this' Problem
There are mainly 3 ways to fix this problem.
Solution 1 - The var = _self method
This first is an old school way of using lexical scope by assigning the this
keyword to variable
outside of the
showBrand()
function.
This solves the solution because any child functions are lexically bound to the execution context of their
parent, in our case it is the getBrandName
.
let car1 = {
brand: 'bmw',
getBrandName: function () {
var _self = this // why not use self? Because it’s a reserved keyword in browsers
let showBrand = function () {
console.log(_self.brand)
}
showBrand()
}
}
car1.getBrandName() // what is the output?
One question you may ask is why not use the self
keyword? It's because it’s a reserved word in
certain
browsers (and evironments). Most people don't actually know this, but if you
mention it you would probably get some extra bonus points for that.
Solution 2 – ES6 Arrow Functions to the Rescue!
Another solution is using the ES6 arrow function on the inner function. All we do here is to just make
showBrand()
an arrow function instead of a regular
function.
Why does this solve our issue? Because...
The showBrand()
arrow-function created in getBrandName()
lexically captures
getBrandName()
's this
at its
call-time. In other words, the ‘this’ in arrow
functions refer to the enclosing function surrounding it. This might sound a little confusing, but imagine doing
a var _self = this in the enclosing function of
showBrand()
, which is getBrandName()
.
let car2 = {
brand: 'bmw',
getBrandName: function () {
let showBrand = () => {
console.log(this.brand)
}
showBrand()
}
}
car2.getBrandName() // bmw
Solution 3 - Explicit (Hard) binding with call, bind, and apply
A third solution is to use an explicit call to the showBrand()
function.
let car3 = {
brand: 'bmw',
getBrandName: function () {
let showBrand = function () {
console.log(this.brand)
}
showBrand.call(this)
}
}
car3.getBrandName() // bmw
The reason why this works is that we explicitly declare our this
context. The this
being the car object. If
you are confused, a really good way to understand
this example is to imagine showBrand.call(this)
as car3.showBrand()
.
Similarly, we can use .bind here as well:
let car4 = {
brand: 'bmw',
getBrandName: function () {
let showBrand = function () {
console.log(this.brand)
}
showBrand.bind(this)()
}
}
car4.getBrandName() // bmw
A Real Life Example with Click Event Handlers
Consider the following code:
<div><button class="button">Show me ‘this’</button></div>
<script type="text/javascript">
let button = document.querySelector('.button')
button.addEventListener('click', function (event) {
setTimeout(function () {
console.log(this)
}, 100)
})
</script>
I’m sure you guys have seen something like this either in jQuery or plain VanillaJS. Can you guess what the output is?
If you have read and understood the article up until this point, then the correct output would be the window object. Why? Remember, the function inside the setTimeout is just an inner function around the addEventListener anonymous function that is being executed.
So how do we fix this? We can pick any of the 3 solutions we have just talked about. How about we use the ES6 arrow function since that’s the easiest to implement. Our code will now look like this
<div><button class="button">Show me ‘this’</button></div>
<script type="text/javascript">
let button = document.querySelector('.button')
button.addEventListener('click', function (event) {
setTimeout(() => {
console.log(this)
}, 100)
})
</script>
The output (on Chrome) looks something like this:
What about another example?
Consider this piece of code:
<div class="time"></div>
<script>
$('.time').each(function () {
setInterval(function () {
console.log(this)
$(this).text(Date.now())
}, 1000)
})
</script>
Conclusion
We have looked at the loss of Implicit Binding, and how any inner
function calls within a function of an object will lose the this
binding and refer to the
default binding of the global window object.
We have also seen the 3 methods to combatting against this issue as follows:
- Use the
var = _self
trick. - Use ES6 Arrow functions instead of normal functions.
- Use Explicit (Hard) bindings with call, bind, or apply.
Lastly, we looked at a real example involving the usage of a setTimeout within a click event handler.