This article attempts to explain how the this keyword works in JavaScript from a new perspective: we assume that arrow functions are real functions, and that ordinary functions are just a special language construct. I think this explanation makes this
better understood, so try it out.
1. Two kinds of functions
This article focuses on two different kinds of functions.
- Normal functions:
function () {}
- The arrow function:
() => {}
1.1 Common functions
We define a normal function like this.
Every normal function implies a this when it is called, which means that in strict mode, the following two expressions are equivalent
This will be overridden (shadowed) if nested in a normal function.
This inside inner() is different from this inside outer(). inner() has its own this. Let’s assume that this is the variable declared on display, then the code would look like this
inner() overrides this in outer(), a rule that also applies to nested scopes
Since ordinary functions always have an implicit argument to this, it might be more appropriate to call these functions “methods”.
1.2 Arrow functions
We define an arrow function like this (I have used a block to define it so that it looks more like a normal function)
If you nest an arrow function inside a normal function, this will not be overridden
Given this characteristic of arrow functions, I would call them “real functions”. Arrow functions are more similar to functions in most other programming languages than to normal functions. It is worth noting that the value of this in an arrow function is not even affected by .call() or anything else. this only depends on the scope in which the arrow function was created. For example
1.3 Common functions as methods
If an ordinary function is a property of an object, then this function is a method
One way to access an object’s properties is through the dot (.) operator. This operator has two different modes.
- Get or set a property:
obj.prop
- Calling the method:
obj.prop(x, y)
The latter is equivalent to.
|
|
As you can see, when a normal function is called, it always carries an implied this. There is also a simpler way of defining special syntax in JavaScript
2. Common errors
We analyse the following common errors with the help of the previous points.
2.1 Error: accessing this in a callback function (in the case of a Promise)
In the code example below, which consists of a Promise, the log “Done” is hit when the asynchronous function cleanupAsync() finishes.
The code executes to line (A) of this.logStatus()
with an error. The error is that the this of this line is different from the this of .performCleanup()
. The callback function’s own this overrides the outer this, meaning that we are using a normal function where we should be using an arrow function. The code works fine after switching to the arrow function.
2.2 Error: Accessing this in a callback function (in the case of .map
)
Similarly, the following code will error on line (A). The reason for this is that the callback function overrides the this of the .prefixNames()
method.
Again, just replace with the arrow function
2.3 Error: Using methods as callbacks
Suppose you have the following UI component code.
In line (A), the UiComponent sets up the response function for the click event. Unfortunately, however, an exception is thrown when the event does fire
|
|
What is the reason for this? In line (A), we are using the normal property access syntax, where . is not a special method call syntax. What this means is that the function present in handleClick is set as the response function, which is roughly equivalent to the following code
This causes an error in line (B) this.name
. So how do we fix this? The problem here is that the dot operator doesn’t simply read the property and call the function when it makes the method call. We need to manually fill in the missing piece with .bind() after we get the method and assign a value to this, as shown in line (A)
This will fix the this problem. The value of this does not change when the function is called.
3. Guidelines to prevent errors
The easiest way is to avoid using normal functions and just use method definitions or arrow functions. But I still like the syntax of function definitions. Function lifting (hoisting) can be useful in some cases. If you avoid using this in a normal function you can also prevent errors. There is an ESLint rule that can help you ensure this.
3.1 Don’t use this as an argument
Some APIs like to pass some parameter-like information through this. I don’t really like this approach. It makes it impossible to use arrow functions and goes against the guidelines mentioned earlier.
As an example, in the following code, beforeEach() passes an API object through this
This function should be changed to
4. Read more
- Blog post “JavaScript’s this: how it works, where it can trip you up”
- Chapter “Callable entities in ECMAScript 6” in “Exploring ES6”