DEV Community

Cover image for 🚀How JavaScript Works (Part 9)? Arrow functions and lexical this
Sam Abaasi
Sam Abaasi

Posted on

🚀How JavaScript Works (Part 9)? Arrow functions and lexical this

The advent of ES6 brought us many exciting features, and among them, arrow functions stood out as a concise and elegant way to write functions. However, while arrow functions have been embraced for their brevity, they come with certain quirks that can be a double-edged sword for developers.

Table of Contents

The Anonymous Nature of Arrow Functions
The Inferred Purpose
The Power of Descriptive Function Names
The Readability Factor
Arrow Functions in the Context of this
The Essence of Lexical this
Arrow Functions and the new Keyword
The Curly Brace Confusion
The Parent Lexical Scope
The Right Tool for Lexical this Behavior
Conclusion
Sources

The Anonymous Nature of Arrow Functions

Arrow functions are inherently anonymous, unlike traditional functions with a distinct name. This anonymity can impact code readability, making it challenging for developers to quickly discern the purpose of a function. Consider this arrow function:

const getId = people.map(person => person.id);
Enter fullscreen mode Exit fullscreen mode

As Kyle Simpson aptly put it, **"The only way to figure out what an arrow function is doing is to read its function body." **This lack of a name can obscure the function's intention, leading to potential confusion for you and other developers working on the code.

The Inferred Purpose

Proponents of arrow functions argue that their purpose is self-evident. However, it's essential to remember that the purpose of an arrow function is inferred rather than explicitly expressed through a well-chosen name. You are required to deduce the function's intent by examining its code, which can sometimes lead to ambiguity.

The Power of Descriptive Function Names

In contrast, when you use a named function, you provide a clear and explicit label for the function's purpose. For example, consider the alternative using a function declaration:

function getIdFromPerson(person) {
  return person.id;
}
const ids = people.map(getIdFromPerson);
Enter fullscreen mode Exit fullscreen mode

In this revised code, the function getIdFromPerson conveys its purpose without ambiguity. It's evident that this function retrieves the id property from a person object. Descriptive names enhance code readability and maintainability, making it easier for you and your team to understand and work with the codebase.

The Readability Factor

While arrow functions offer brevity, they can sometimes compromise readability. Self-explanatory code, where the purpose of a function is evident from its name, often proves invaluable. Arrow functions' shorter syntax may initially appear more straightforward, but the brevity can come at the cost of understanding and maintainability. Moreover, arrow functions come in various syntax variations, leading to potential confusion in certain contexts.

Arrow Functions in the Context of this

However, it's crucial to acknowledge that arrow functions bring a unique characteristic called "lexical this behaviour." This behaviour can be immensely valuable in specific situations.
Consider this example:

const workshop = {
    topic: "JavaScript",
    ask: function () {
        setTimeout(() => {
            console.log(`Welcome to the ${this.topic} workshop!`);
        }, 1000);
    },
};
workshop.ask(); // Outputs: Welcome to the JavaScript workshop!
Enter fullscreen mode Exit fullscreen mode

In this case, the ask method of the workshop object contains an arrow function passed to setTimeout. Surprisingly, within the arrow function, the this keyword correctly points to the workshop object. This phenomenon is the essence of "lexical this behavior."

Now, let's dive deeper into what "lexical this" means:

An arrow function does not define a this keyword. In fact, it doesn't have a this keyword at all; it treats this like any other variable.

The Essence of Lexical this

In essence, lexical this means that an arrow function will keep climbing up the scope chain until it locates a function with a defined this keyword. The this keyword in the arrow function is determined by the function containing it. In the example, it looks up one level in scope and finds the ask function.

This understanding is crucial because it helps you avoid incorrect thinking, which can lead to bugs in your code. By thinking in alignment with JavaScript's design and the language specification (the spec), you can ensure that your code behaves as expected.

The JavaScript language specification confirms that an arrow function does not define local bindings for arguments, super, this, or new.target. This specification is a key point of reference, and adhering to it eliminates misconceptions and potential issues in your code.

Arrow Functions and the new Keyword

One fascinating aspect of arrow functions is their behavior concerning the new keyword. If you recall from earlier in this series, the new keyword takes precedence over a hardbound function. That means, for some unusual reason, if you call new on a hardbound function, it can override the hard binding and become the new object. However, this is not the case with arrow functions. Calling new on an arrow function results in an exception, as arrow functions are not hardbound functions.

"TypeError: function is not a constructor"

Understanding this distinction is vital for maintaining code clarity and preventing unexpected behavior in edge cases.

The Curly Brace Confusion

One of the persistent frustrations among developers is the assumption that curly braces {} imply a scope. We often associate them with blocks, function bodies, or scopes. However, it's essential to understand that not every set of curly braces denotes a scope.
Consider this example:

const workshop = {
    teacher: "Kyle",
    ask: (question) => {
        console.log(this.teacher, question} 
    },
};
Enter fullscreen mode Exit fullscreen mode

At first glance, it appears that this function is enclosed in a scope. Still, this is not the case. These curly braces merely define the function body; they don't create a scope. Arrow functions don't introduce a new scope like regular functions do.

The Parent Lexical Scope

One common misconception is that arrow functions should inherit the scope in which they are defined. For example, you might expect an arrow function defined within an object to capture that object as its this context. However, this isn't the case.

Arrow functions are not context-aware; they don't possess their own this. Instead, they resolve this lexically, which means they inherit the this from their parent lexical scope. In the global scope, this would typically be the global object.

For example, in the workshop object:

workshop.ask("What happened to 'this'?")
// undefined What happened to 'this'?
Enter fullscreen mode Exit fullscreen mode

The this inside the arrow function does not point to the workshop object but inherits the this from the global scope. So, it logs undefined.

The Right Tool for Lexical this Behavior

Arrow functions are not a one-size-fits-all solution; they are the right tool for maintaining the this context from their parent scope. For instance, when you need to pass a function as a callback to methods like setTimeout, using an arrow function ensures that this remains consistent with the context in which it's defined.
Let's consider a scenario:

workshop.ask.call(workshop, "Still no 'this'");
// undefined Still no 'this'
Enter fullscreen mode Exit fullscreen mode

Here, we explicitly use call to set the this context to the workshop object. However, the arrow function still resolves this lexically, ignoring the provided context. It logs undefined.

Conclusion

The key takeaway here is that arrow functions are not a one-size-fits-all solution. While they excel in certain situations, such as when you require lexical this behaviour, they may not be the best choice for every use case. It's crucial to understand their behaviour and limitations fully. By doing so, you can wield arrow functions effectively, leveraging their strengths while avoiding their potential pitfalls. Ultimately, mastering this powerful feature will help you become a more proficient and versatile JavaScript developer.

Sources

Kyle Simpson's "You Don't Know JS"
MDN Web Docs - The Mozilla Developer Network (MDN)

Top comments (0)