The Strange Case of Arrow Functions and Mr. Context: Understanding “This” in Arrow Functions — Lexical Scope Vs Dynamic Scope

Sergio Valverde
Frontend Weekly
Published in
6 min readDec 26, 2017

Arrow functions, and the way they handle context, are great for some use cases, such as being used as callbacks for event handlers, or just passing them around an application without losing their context. However, the way the “this” keyword really works in arrow functions is not as clear as it might first seem. I wanted to write this article to hopefully shine a bit more light on how arrow functions really determine their context. This article doesn’t cover the basic syntax for arrow functions, so I’m assuming you are familiarized with it already.

Ever since arrow functions were introduced in ES6, they have been a useful tool to have a bit more control of the context of a function. One of their main characteristics is that they have a “fixed” context; they retain the same context as its lexical environment (the context of the function’s surroundings at the time of its creation).

I wrote a separate article explaining in more detail how the context of regular functions in general can change depending on how they are created and called, check that out for a more detailed explanation. In this article, I will focus on the peculiarities of arrow functions.

One example that I covered in the previous article was this one:

Note: A method inside a constructor is usually not a good idea, see why below.

In this example, we define an arrow function assigned to the property logThis of any instance created by the constructor function Test(). The point of the example was to show that the context of the arrow function will remain being the instance “test”, even when using it as a callback for the click event, but there are several reasons why doing this is not really a good idea.

Since we are no longer working with events and buttons, we can refactor it to make it a bit simpler to understand, like this:

In this version of the example, the function we are testing just returns the “this” value.

This is a bit beside the topic for this article, but it’s important to clarify that declaring functions inside a constructor is usually not ideal and not a common practice, due to how constructors functions work in JavaScript. If you want to use prototypal object orientation and use constructor functions like these, it is better to define methods as a property of the prototype of the constructor, like so:

Notice that the variable “thisValue” on line 12 evaluates to the instance with name “The new instance”

This is because the constructor function’s prototype will be used as the prototype for any instances created, they will all share the same prototype object. Therefore, the prototype’s methods are only created once, and they are shared by all the instances created with that constructor. If you define a method directly inside the constructor function, JavaScript will create a copy of the function for each new instance, and this is in most cases unneeded duplication.

Now just out of curiosity, let’s see what happens if we try to use arrow functions to define prototype methods:

Same example as before, but returnThis is now an arrow function. Note how “thisValue” is now different. You cannot see it clearly in the information displayed by the inline evaluation, but the context becomes the global context (window object in browsers).

It breaks! As you can see, the context of the function no longer references the instance. This is definitely not what we wanted. Before getting into the details of why this scenario doesn’t work at all, it is better to see the same behavior with a simpler example:

Chewbarka Example #1: Here we have a dog with a bark method, which is defined as a regular function, and works as expected when executed on line 9 (it prints “woof!!”, the inline evaluator shows the result directly on line 6).
Chewbarka Example #2: Same as above, but this time bark is defined as an arrow function. Note that when called it prints “undefined!!”, because the context is no longer the instance, so it cannot find a property “sound”.

In the previous article, I explained that scope and context are two different things. The context refers to a special object within which any function is considered to run, referenced by “this”, while scope relates to the accessibility of variables and their lifetime. However, to explain why the context for arrow functions behaves the way it does, we need to go back to the topic of scope for a bit.

In programming in general, there are two types of scoping: Lexical scoping and dynamic scoping. Lexical scoping means determining the scope of variables according to the code structure (a.k.a lexical structure) at compilation time, before it’s executed. Then when the code runs, the scope for all variables is already determined. In dynamic scope variables can end up being scoped in a different way, because it determines this scope at runtime. This depends on how the variables are being used, and which variables the stack holds at any given time.

Javascript uses lexical scope: If you define a function inside another function, the inner function will always have access to any variables defined in the outer function’s scope, regardless of how the inner function is executed (which would be runtime). This is exactly how closures work.

(Side note: Even though Javascript is an interpreted language, it still goes through a “compilation” phase where it interprets all the code before running it)

Interestingly, the behavior of a regular (not arrow) function’s context (the this object) in JavaScript works in a similar way to dynamic scope: The context is defined at runtime, depending on how the function is called. This is a critical difference between context and scope in Javascript.

Going back to arrow functions, the reason why their context behaves differently from regular functions is because it is calculated in a lexical fashion at compilation, instead of being dynamic at runtime. Hence, it doesn’t matter how they are called, they’ll just retain the context that was determined lexically.

In our Chewbarka example #1, the bark function’s context is determined when it runs: It is executed as a property of Chewbarka, and that’s why “this” points to Chewbarka. If we defined another variable “test” referencing exactly the same function, but calling it as a stand alone function, then it would no longer work:

test is a reference to the same bark function, but their context is different since it’s called differently.

In our Chewbarka example #2, since bark is now an arrow function, it doesn’t matter how it is called, and the context is set to be whatever the context was at this part of the application when this code was interpreted. Intuitively we might think that it would refer to the object literal where it is being declared, but this is a mistake.

Creating an object literal with properties and methods is really equivalent to creating an empty object literal and specifying the properties afterwards:

Following the same guidelines that I mentioned here to determine what is the value for “this” at any given point in the code, in this example we are not inside any function, so “this” points to the global context. This is why the context for the arrow function bark is permanently bound to this global context. When we tried to use arrow functions to define prototype methods, the same thing happened.

As a conclusion therefore, it is not really recommended to use arrow functions to declare methods for objects. The only case where they do work as methods is if you use them directly inside a constructor function, but as mentioned, in general a constructor shouldn’t include methods. The lexical nature of the context of arrow functions is much more well suited for other use cases, like using them as helper functions, or as event callbacks.

--

--