Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 23

Arrow Functions: Syntax and Lexical 'this'

Recommended resources

Hello! Welcome to your next lesson in our JavaScript module.

In the previous lesson, we covered a crucial concept in modern JavaScript: declaring variables with let and const. You learned about their block-scoping behavior and why it's preferred over the function-scoping of var. This idea of scope is central to writing clean and predictable code.

Today, we'll continue our journey into modern JS by looking at functions. Specifically, our goal is to write functions using arrow syntax and demonstrate their lexical 'this' behavior compared to traditional functions. You'll learn not just a new, more concise way to write functions, but also understand a fundamental difference in their behavior that solves a common category of bugs in JavaScript.

1. The Syntax of Arrow Functions

Let's start with the basics: how to write an arrow function. Introduced in ES6, they provide a more compact syntax compared to traditional function expressions.

For a clear walkthrough of how to convert different types of traditional functions into arrow functions, watch the first part of this video from Web Dev Simplified.

JavaScript ES6 Arrow Functions Tutorial

This video provides a great visual guide to the syntax of arrow functions, showing how to convert named functions with different numbers of parameters.

Watch from the beginning to 04:24. As you watch, pay attention to these transformations: Replacing the function keyword. How parentheses () are handled with zero, one, or multiple parameters. How curly braces {} and the return keyword can be omitted for single-line functions (this is called an implicit return).

Hands-On: Converting Functions

Let's practice this. In your project, you can either continue using variables.js or rename it to something like concepts.js for this module. Clear out the old code and add these traditional functions:

// Traditional function with multiple parameters
function sum(a, b) {
  return a + b;
}

// Traditional function with one parameter
function double(n) {
  return n * 2;
}

// Traditional function with no parameters
function sayHello() {
  console.log("Hello, World!");
}

Now, try to convert each one into an arrow function on your own. Your converted functions should look something like this:

// Converted sum function
const sumArrow = (a, b) => a + b;

// Converted double function
const doubleArrow = n => n * 2;

// Converted sayHello function
const sayHelloArrow = () => console.log("Hello, World!");

// Test them!
console.log(sumArrow(5, 10)); // Should print 15
console.log(doubleArrow(7)); // Should print 14
sayHelloArrow(); // Should print "Hello, World!"

Notice how much cleaner and more concise the code becomes, especially for simple, single-line functions. This conciseness is particularly useful for callback functions, which you'll encounter frequently in JavaScript.


2. The this Keyword: The Biggest Difference

Beyond syntax, the most important difference between traditional and arrow functions is how they handle the this keyword. This is a core concept that addresses your goal of understanding the "why" behind modern practices.

  • In a traditional function, the value of this is determined by how the function is called. This is known as dynamic binding. It can change depending on the context (e.g., called as a method of an object, called as a standalone function, etc.). This dynamic nature is a frequent source of confusion and bugs.
  • In an arrow function, this is determined by where the function is defined. It lexically inherits this from its surrounding scope. It doesn't have its own this context.

This might seem abstract, so let's see a practical example where this difference becomes critical.

The Problem: Losing this in Callbacks

A classic problem in JavaScript is losing the this context inside a callback function, like one used with setTimeout.

Add this code to your concepts.js file and run it:

const user = {
  name: "Alex",
  // A traditional function as a method
  greetRegular: function() {
    console.log(`Hello, I'm ${this.name}.`); // 'this' correctly refers to the 'user' object here.

    setTimeout(function() {
      // This is a new function call, its 'this' is not the 'user' object anymore!
      console.log(`My name is ${this.name}.`); 
    }, 1000);
  }
};

user.greetRegular();

When you run this, you'll see:

  1. Hello, I'm Alex.
  2. My name is undefined. (or nothing, depending on the environment)

Why? Inside the setTimeout callback, the traditional function is not being called as a method of the user object. It's being invoked by the setTimeout mechanism, which doesn't set the this context to our user. In a browser, this defaults to the global window object, which has no name property.

The Solution: Lexical this with Arrow Functions

Now, let's fix this using an arrow function. The video you watched earlier has a perfect demonstration of this exact problem and solution.

JavaScript ES6 Arrow Functions Tutorial

This section of the video clearly explains the this binding problem using a setTimeout example and shows how an arrow function elegantly solves it.

Watch from 05:38 to 09:00. The video uses a class, but the principle is identical to our user object example. Focus on the explanation of why this.name works in the arrow function but fails in the regular function.

Let's apply this fix to our user object. Create a new method using an arrow function for the callback.

const user = {
  name: "Alex",
  greetArrow: function() {
    console.log(`Hello, I'm ${this.name}.`); // 'this' correctly refers to 'user'.

    setTimeout(() => {
      // The arrow function inherits 'this' from the surrounding 'greetArrow' scope.
      // So, 'this' here is the same 'this' as outside.
      console.log(`My name is ${this.name}.`);
    }, 1000);
  }
};

user.greetArrow();

When you run this version, you'll see the expected output after one second:

  1. Hello, I'm Alex.
  2. My name is Alex.

The arrow function didn't create its own this. It simply inherited the this from its parent scope (greetArrow), where this correctly pointed to the user object. This lexical behavior is much more intuitive and is the primary reason arrow functions are widely used today.

This article provides a great written explanation of the same concept.

How to Use JavaScript Arrow Functions – Explained in Detail

For a detailed textual explanation with another practical example, read this section from freeCodeCamp's article.

Read the section titled "Arrow Functions Don’t Have this Binding". The example uses a .forEach callback, which is another perfect illustration of where lexical this is incredibly useful.


3. When NOT to Use Arrow Functions

While powerful, arrow functions are not a complete replacement for traditional functions. Because they don't have their own this context, they are generally unsuitable for methods defined directly on an object.

Consider this example:

JavaScript 'this' Binding: Arrow Functions vs. Regular Functions
This image illustrates why a regular function is needed for an object method that relies on `this`. The arrow function `getLikes` fails because its `this` does not refer to the `codesmith` object, while the regular function `setLikes` works correctly.

If you define an object method using an arrow function, its this will refer to the scope where the object itself was defined (often the global scope), not the object.

// Incorrect use of arrow function for a method
const movie = {
  title: 'Inception',
  printTitle: () => {
    // 'this' here is NOT the 'movie' object. It's the global 'window' object.
    console.log(`The title is ${this.title}`); 
  }
};

movie.printTitle(); // Prints "The title is undefined"

The Rule of Thumb:

  • Use arrow functions for callbacks or any nested function where you need to preserve the this from the outer scope.
  • Use traditional functions (function methodName() { ... }) for methods that are defined directly on an object and need to refer to that object's properties with this.

Conclusion

Great work! You've tackled one of the most important and initially confusing topics in JavaScript. Understanding this is a major step toward writing professional, bug-free code.

Key Takeaways:

  • Arrow functions offer a concise syntax, especially for single-line callbacks with implicit returns.
  • The key difference is this binding:
    • Traditional functions have a dynamic this, determined by how they are called.
    • Arrow functions have a lexical this, inherited from the scope where they are defined.
  • This makes arrow functions perfect for callbacks inside methods (like with setTimeout or event listeners) to avoid losing the this context.
  • Avoid using arrow functions for object methods that need to refer to the object itself.

In our next lesson, we will explore methods for working with arrays and objects, such as map, filter, destructuring, and the spread syntax. You will see just how frequently arrow functions are used with these methods, making your code both powerful and readable.