The problem with `this` in JS
Both starters and experienced developers in JS have one thing in common. Sometimes this
behaves in a way you don't expect and you don't know why. But what exactly is so special about this little keyword in Javascript?
First the good thing
In the simplest form this
behaves exactly the way you'd expect it to work.
Let's take this class as an example.
class Output {constructor() {this.prop = "Hello world";}print() {console.log(this.prop)}}
This class defines a method that prints its prop
property to the console.
To make it clear we're referencing the prop
from this exact instance we have to use this
.
If you call this method you'll receive the expected output
You might be wondering now: So what's the problem? Works as expected!
Then take a look at the next example.
const output = new Output();const printCallback = output.print;
We're creating an object of Output
and store a reference of its print
method inside a constant.
If you call this callback method you get the expected output of...
Wow that's unexpected, isn't it? Why does that happen?
Well, it turns out that although the this
keyword, same as in other languages, means we want to reference this object, in Javascript the value of this
depends on where you call the function.
So when you call output.print()
the this
inside of the print
method is set to the output
object.
That's why you will get the expected output.
If you create a callback to this method though, when you call it the value of this
is, again, set to whatever calls the function.
And because the callback was created in the global scope (in Deno we don't have a global this
), the callback code can't find the required property on an undefined object and gives us an error.
But there must be a way how we can pass callbacks and still reference the same property as when we defined the method.
Arrow functions come to rescue
As you might know, in Javascript we have two options to create a function.
- by function declaration:
function name(...)
- by arrow function:
() => {...}
If you've worked with arrow functions before you might have already used one of their features without actually looking at it.
When defining an arrow function, the value of this
remembers whatever it was when the arrow function was created.
This means that when passing it around, e.g. as a callback, no matter from where you call this function, you will always get the same result.
class Output {constructor() {this.prop = "Hello world";this.print = () => console.log(this.prop);}}
This code looks almost identical to the one above, but pay attention to this detail this.prop = () => ...
Because we use an arrow function instead of a regular function declaration, we get completely different behavior when it comes to the this
keyword.
Storing and calling this method as a callback now will lead to the following:
Yes 👍 This is exactly the behavior we wanted and expected.
When do you need this?
Well depending on what you do, you might encounter this problem quite frequently. I stumbled across this issue when I first started with React.
Back then I build Class-Components
and defined all my callback methods as functions of my component and passed them down via props to other components.
But because I didn't fully understand the behavior of this
I got errors whenever the callback got triggered.
One more solution
Let's say you just don't want to use an arrow function. What else can you do to prevent this issue from happening?
Luckily, Javascript has a special function on its Prototype
called bind
.
Whenever you'd want to keep the this
value from where the function was defined, you'd simply call:
yourFunction.bind(objectToBindTo);
This function returns a completely new function reference,
that will invoke the exact same code you defined in the original function.
But now the value of this
inside of the function is set to the object you pass to the bind
method.
No more unexpected errors when you use this
inside of the function, you can simply and safely pass this
new function around without worrying about what or who the caller is.
Conclusion
No matter what path you choose, arrow functions, or binding the value, it's always worth noting spending time to understand what really happens in Javascript will save you time and a headache later.