Object Oriented Javascript, this, and bind()
Recently I made a SPA using a JavaScript, CSS, and HTML front-end with a Rails API back-end. I tried to make my JavaScript as object-oriented as possible and I had several event listeners and one setInterval which required me to understand execution context, the word ‘this’, and bind.
Execution Context
Let’s start with execution context. What is it? Execution context is the environment in which the JavaScript code is executed. Seems like a pretty redundant explanation. To put it simply the execution context refers to the arguments given to a function, the variables it has access to given the scope, and scope chain, and the value of the this object.
What’s “this”?
According to w3schools:
The JavaScript
this
keyword refers to the object it belongs to.It has different values depending on where it is used:
- In a method,
this
refers to the owner object.- Alone,
this
refers to the global object.- In a function,
this
refers to the global object.- In a function, in strict mode,
this
isundefined
.- In an event,
this
refers to the element that received the event.- Methods like
call()
, andapply()
can referthis
to any object.
This is how the this object ties into execution context. Essentially this refers to the execution context. If you declare function globally, the execution context, and therefor the this object, will be the global object. If you are using a callback function inside of an eventListener for instance, the this keyword inside of the callback function will refer to the element you are calling the eventListener on. Most importantly, in my case of using Object Oriented JavaScript, if you declare a function inside of a class, that function is called a method and this, inside of that method, will refer to the instance of the class(or the class itself if you are declaring a static method).
Seems pretty cut and dry, right? Well, in my case where things became confusing was when I was trying to use methods as the callback functions inside of eventListeners. Let’s use a real example from my code. For some context, my app was a tamagotchi knock-off called dottomodachi. There is a button on the screen called meal, that when clicked should increase the hunger and weight meter of a dottomodachi. So I had this method:
meal(){this.weightMeter = this.weightMeter + 1this.hungerMeter = this.hungerMeter + 5//below is a method to update how full the hunger and weight meters appear on the DOMthis.renderProgressBars()}
Which was designed as a callback function for this eventListener:
//this is all inside of a renderDottomodachi() method, so below "this" is in reference to the instance of a dottomodachi that renderDottomodachi() has been called on.let mealButton = document.createElement('button')mealButton.addEventListener('click', this.meal)
What do you think will happen when a user clicks the mealButton? Well… with the code as it currently stands:
Uncaught TypeError: this.renderProgressBars is not a function
at HTMLButtonElement.meal
What’s going on here? I thought that inside a method, the this object refers to the owner object, in this case, our instance of a dottomodachi. We have to remember that in an event the this object refers to the element that received the event. In this case, the button itself. Therefor it’s trying to call renderProgressBars()
on the button, which isn’t going to work. You can see in the above example, while we were in the renderDottomodachi()
method this did in fact refer to the instance of a dottomodachi, it knew to go to our meal()
method, but once we were inside of that method, the execution context was that of the eventListener and this became the button which it was called on. How do we fix this? Well there are a few ways.
Arrow Function Wrapper
Initially what I did to fix this was use an anonymous arrow function as my callback function and just call the meal()
method inside of it. Like so:
mealButton.addEventListener('click', (e)=> {
this.meal() })
Why does this work? Well arrow functions bind their current execution context to the this object. So whenever they are called, this will still refer to the execution context in which they are declared. So when we called this.meal
the execution context was still that of the renderDottomodachi()
method. By declaring an anonymous arrow function there we are binding the this object to the execution context of renderDottomodachi()
which was called on an instance of a dottomodachi. So when it reaches this.renderProgressBars()
it will call that method on that instance instead of trying to call it on the button. Is this optimal? No. We are declaring an anonymous function of which the sole purpose is to call another function. So what is another option?
Declare Our Methods Using Arrow Function Expression
So we just learned that when we use arrow function expression, we bind the this object to the execution context in which the function/method was declared. Therefor another solution would be to declare our meal()
method using this syntax. This means every time a new instance of a dottomodachi is created, it comes with a method called meal()
in which this is always equal to the instance itself.
meal = () => { this.weightMeter = this.weightMeter + 1this.hungerMeter = this.hungerMeter + 5//below is a method to update how full the hunger and weight meters appear on the DOMthis.renderProgressBars()}
Does this work? Absolutely. Is it the best way? According to some, no it is not. So what else is there to do?
Bind!!!
So what if instead of relying on arrow function expression to bind this to a certain execution context, we did it ourselves? In comes function.prototype.bild()
. What does bind do? How can it help? Bind will return a copy of the function with the this object set to whatever it’s first argument is, therefor:
mealButton.addEventListener('click', this.meal.bind(this))
this means that the callback function will be a copy of the meal()
method where this will always equal the instance of a dottomogachi that renderDottomogachi()
was called on. Another way to achieve the same result:
let boundMeal = this.meal.bind(this)
mealButton.addEventListener('click', boundMeal)
The only difference is that in the first example the bound copy of the meal()
method is not assigned to a variable.
Closing Thoughts
So ultimately, why did I go with choosing bind over an arrow function expression? I felt the syntax was more expressive than wrapping it in an anonymous arrow function, and using bind in that one instance rather than binding the execution context when declaring the meal()
function allowed me to bind the this object in that one circumstance rather than permanently altering it(which I think could lead to some unintended consequences later down the line). What do you think? Certainly there is a lot more to the this object, execution context, and bind than I have written here and a lot more that I hope to learn in the future.