Skip to content
ExponaBLOG
JAVASCRIPT & TYPESCRIPT · DAY 009 / 100

Arrow functions and 'this': patterns, pitfalls and fixes

Arrow functions capture 'this' from where they're defined.

Expona Admin7 min read

Opening Hook

If you've debugged a callback inside a class method and been surprised by what this pointed to, you've felt the friction of JavaScript's context binding. Arrow functions are often presented as a cleaner syntax for regular functions, but their behavior with this is fundamentally different—and that difference can be your best friend or your worst enemy depending on how you use them. Understanding arrow functions and 'this': patterns, pitfalls and fixes is essential for writing predictable, maintainable code across modern JavaScript and TypeScript applications.

How Arrow Functions Capture 'this'

The core principle is simple but powerful: arrow functions do not have their own this binding. Instead, they lexically capture this from the surrounding scope where they're defined. This is different from regular functions, which bind this dynamically based on how they're called.

Consider this regular function:

typescript
const obj = {
  name: 'Alice',
  greet: function() {
    console.log(this.name); // 'this' depends on how greet is called
  }
};

obj.greet(); // 'this' is obj, prints 'Alice'
const fn = obj.greet;
fn(); // 'this' is undefined (or global in non-strict), error or undefined

Now with an arrow function:

typescript
const obj = {
  name: 'Alice',
  greet: () => {
    console.log(this.name); // 'this' captured from enclosing scope (global or module)
  }
};

obj.greet(); // 'this' is NOT obj, it's whatever was in scope when greet was defined

In the arrow function example, this is captured from the module or global scope, not from obj. This is the lexical binding behavior that makes arrow functions predictable in some contexts and problematic in others.

Great for Callbacks Inside Class Methods

This is where arrow functions shine. In class methods, you often pass callbacks to event listeners, timers, or array methods. A regular function callback would lose its this binding, but an arrow function preserves it—automatically.

Consider an event handler in a class:

typescript
class UserComponent {
  private name = 'Charlie';

  setupButton(element: HTMLElement) {
    // With regular function: this.name would be undefined
    element.addEventListener('click', function() {
      console.log(this.name); // 'this' is the element, not the class instance
    });

    // With arrow function: this is captured from the class method
    element.addEventListener('click', () => {
      console.log(this.name); // 'this' is the class instance, prints 'Charlie'
    });
  }
}

new UserComponent().setupButton(document.querySelector('button')!);

The arrow function callback automatically captures this from the class method scope. No need for .bind(this), no need for a temporary variable. This pattern is extremely common in React, Angular, and other frameworks:

typescript
class UserForm {
  private userData = { email: 'user@example.com' };

  render() {
    return (
      <input
        onChange={(e) => {
          // 'this' is the component, we can access this.userData
          this.validateEmail(e.target.value);
        }}
      />
    );
  }

  validateEmail(email: string) {
    console.log(this.userData.email); // Works as expected
  }
}

Without the arrow function, this would be undefined inside the onChange handler, and accessing this.userData would throw an error.

Bad as Object Methods That Need 'this'

Arrow functions are the wrong choice when you're defining methods on plain objects or classes that need to access instance data via this. The arrow function captures this from the enclosing scope, which is usually the global scope or module scope—not the object itself.

Here's a problematic pattern:

typescript
const user = {
  name: 'Diana',
  age: 28,
  // DON'T DO THIS
  greet: () => {
    console.log(`Hi, I'm ${this.name}`); // 'this' is global/undefined, not the user object
  }
};

user.greet(); // Prints "Hi, I'm undefined" (in strict mode) or something unexpected

The fix is to use a regular function or a method shorthand:

typescript
const user = {
  name: 'Diana',
  age: 28,
  // DO THIS instead
  greet() {
    console.log(`Hi, I'm ${this.name}`); // 'this' is the user object
  }
};

user.greet(); // Prints "Hi, I'm Diana"

Or in a class:

typescript
class User {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  // Use regular method
  greet() {
    console.log(`Hi, I'm ${this.name}`);
  }
}

new User('Diana').greet(); // Prints "Hi, I'm Diana"

If you define methods as arrow functions in a class constructor, they'll work but at a memory cost—each instance gets its own copy of the method instead of sharing it on the prototype. This is only worth doing if you specifically need the lexical this binding, which is rare for regular methods.

No Arguments Object

Arrow functions don't have their own arguments object. Regular functions receive an implicit arguments variable that collects all passed arguments, even ones not declared in the parameter list.

typescript
function regular(a, b) {
  console.log(arguments); // [1, 2, 3] - includes all arguments
  console.log(arguments.length); // 3
}

regular(1, 2, 3);

Arrow functions don't have this:

typescript
const arrow = (a, b) => {
  console.log(arguments); // ReferenceError: arguments is not defined
};

arrow(1, 2, 3);

In modern JavaScript, this is rarely a problem because rest parameters are clearer and more explicit:

typescript
const arrow = (a, b, ...rest) => {
  console.log(a, b, rest); // 1, 2, [3]
};

arrow(1, 2, 3);

Rest parameters work in both regular and arrow functions and are the preferred pattern today. If you find yourself needing the raw arguments object, you're likely missing a clearer way to express your intent.

Can't Be Used with 'new'

Arrow functions cannot be used as constructors. Calling an arrow function with new throws a TypeError. Regular functions become constructors by convention; they're designed to initialize an instance and set up its prototype chain.

typescript
const ArrowFunction = () => {
  this.value = 42;
};

// TypeError: ArrowFunction is not a constructor
new ArrowFunction();

Regular functions and classes work fine:

typescript
function RegularFunction() {
  this.value = 42;
}

new RegularFunction(); // Works, creates an instance

class MyClass {
  value = 42;
}

new MyClass(); // Works

This limitation rarely affects real-world code because you should use classes for any constructor-like behavior anyway. But it's useful to know if you're writing a library or framework that needs to distinguish between callable functions and constructors.

Common Mistakes

Here are patterns we've seen cause confusion in production codebases:

  • Arrow function as a method expecting 'this': Defining a method in a class or object with an arrow function, then trying to access properties via this. Always use regular methods or method shorthand for these.
  • Forgetting lexical scoping in callbacks: Writing an arrow function callback and assuming this will be something other than what was in scope when the function was defined. Verify the scope chain if behavior is unexpected.
  • Mixing arrow and regular functions inconsistently: Using arrow functions for some callbacks and regular functions with .bind(this) for others in the same codebase. Pick a pattern and stick to it for readability.
  • Reassigning arrow function callbacks: Storing an arrow function callback in a property, then reassigning it later expecting this to change. It won't—this is locked at definition time.

Key Takeaways

  • Arrow functions capture this lexically from their definition scope, not dynamically from their call site.
  • Use arrow functions for callbacks inside class methods or event handlers to preserve the instance's this.
  • Use regular methods (function or shorthand syntax) for object and class methods that need to access this as the instance.
  • Arrow functions don't have their own arguments object—use rest parameters instead.
  • Arrow functions cannot be used with new; use classes or regular constructors if you need to instantiate objects.
  • When in doubt, check where this is captured: look at the enclosing scope where the arrow function is defined, not where it's called.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles