DEV Community

Maxim Logunov
Maxim Logunov

Posted on • Edited on

Understanding `this` in JavaScript: The Complete Guide

The this keyword is one of the most important — and most misunderstood — concepts in JavaScript.

Many developers memorize rules like:

  • “Arrow functions don’t have their own this
  • “Regular functions depend on how they are called”
  • “Use arrow functions in callbacks”

But without understanding why JavaScript behaves this way internally, confusion quickly appears in real projects.

This article explains everything step by step:

  • how this works in regular functions,
  • how arrow functions “capture” this,
  • what “lexical binding” actually means,
  • how call, apply, and bind affect this,
  • how classes actually work,
  • the difference between class methods and class fields,
  • common mistakes,
  • advantages and disadvantages of each approach.

After reading this guide, you should have a complete mental model of this in JavaScript.


1. What Is this?

In JavaScript, this is a special keyword that refers to an object associated with the current function execution.

The important thing is:

this is determined differently depending on the type of function.

There are two completely different behaviors:

  1. Regular functions
  • this depends on how the function is called.
  1. Arrow functions
  • this is taken from the surrounding scope when the function is created.

This difference is the key to understanding everything else.


2. this in Regular Functions

Basic Rule

For regular functions:

this is determined at call time.

That means JavaScript looks at how the function was invoked.


3. Global Context

In Browsers

console.log(this);
Enter fullscreen mode Exit fullscreen mode

In a browser outside strict mode:

window
Enter fullscreen mode Exit fullscreen mode

because the global object in browsers is window.


In Strict Mode

"use strict";

function test() {
    console.log(this);
}

test();
Enter fullscreen mode Exit fullscreen mode

Output:

undefined
Enter fullscreen mode Exit fullscreen mode

In strict mode, regular functions called normally get undefined as this.

This behavior prevents many bugs.


4. Method Calls

When a function is called as a method of an object:

const user = {
    name: "John",

    sayHello: function () {
        console.log(this.name);
    }
};

user.sayHello();
Enter fullscreen mode Exit fullscreen mode

Output:

John
Enter fullscreen mode Exit fullscreen mode

Here:

this === user
Enter fullscreen mode Exit fullscreen mode

because the function was called through user.


5. The Call-Site Rule

This is the most important rule for regular functions.

JavaScript determines this from the expression before the dot:

obj.method()
Enter fullscreen mode Exit fullscreen mode

Inside method:

this === obj
Enter fullscreen mode Exit fullscreen mode

6. Losing this

A very common mistake:

const user = {
    name: "John",

    sayHello() {
        console.log(this.name);
    }
};

const fn = user.sayHello;

fn();
Enter fullscreen mode Exit fullscreen mode

Output in strict mode:

undefined
Enter fullscreen mode Exit fullscreen mode

Why?

Because the function is no longer called as:

user.sayHello()
Enter fullscreen mode Exit fullscreen mode

It is called as:

fn()
Enter fullscreen mode Exit fullscreen mode

There is no owning object anymore.


7. call, apply, and bind

JavaScript provides methods for explicitly setting this.


call

function greet() {
    console.log(this.name);
}

const user = { name: "Alice" };

greet.call(user);
Enter fullscreen mode Exit fullscreen mode

Output:

Alice
Enter fullscreen mode Exit fullscreen mode

call immediately invokes the function with a specific this.


apply

Works like call, but arguments are passed as an array.

fn.apply(obj, [arg1, arg2]);
Enter fullscreen mode Exit fullscreen mode

bind

bind does not call the function immediately.

Instead, it creates a new function with permanently bound this.

function greet() {
    console.log(this.name);
}

const user = { name: "Alice" };

const bound = greet.bind(user);

bound();
Enter fullscreen mode Exit fullscreen mode

Output:

Alice
Enter fullscreen mode Exit fullscreen mode

8. How JavaScript Internally Handles this

For regular functions, JavaScript creates this dynamically during execution.

Simplified idea:

function fn() {}
Enter fullscreen mode Exit fullscreen mode

When called:

obj.fn()
Enter fullscreen mode Exit fullscreen mode

JavaScript internally behaves roughly like:

fn.[[ThisValue]] = obj
Enter fullscreen mode Exit fullscreen mode

The important part:

this is NOT stored inside the function itself.

It is assigned every time the function runs.

That is why the same function can have different this values.

Example:

function show() {
    console.log(this.name);
}

const user1 = { name: "John", show };
const user2 = { name: "Alice", show };

user1.show(); // John
user2.show(); // Alice
Enter fullscreen mode Exit fullscreen mode

Same function.
Different this.


9. Arrow Functions: Completely Different Behavior

Arrow functions do NOT work like regular functions.

They do not create their own this.

Instead:

Arrow functions capture this from the surrounding lexical scope at creation time.

This is called lexical this.


10. What “Lexical” Actually Means

“Lexical” means:

determined by where code is written.

Not by how it is called.

This is the same principle used for variables in closures.

Example:

function outer() {
    const x = 10;

    return function inner() {
        console.log(x);
    };
}
Enter fullscreen mode Exit fullscreen mode

inner remembers x from where it was created.

Arrow functions do the same with this.


11. How Arrow Functions Capture this

Example:

const user = {
    name: "John",

    regularFunction: function () {
        console.log(this);

        const arrow = () => {
            console.log(this);
        };

        arrow();
    }
};

user.regularFunction();
Enter fullscreen mode Exit fullscreen mode

Output:

user
user
Enter fullscreen mode Exit fullscreen mode

Why?

Because the arrow function does not create its own this.

When the arrow function is created, JavaScript looks outward:

  • Is there a surrounding function with this?
  • Yes → regularFunction
  • regularFunction has this === user

The arrow function permanently stores that reference.


12. Internal Mental Model of Arrow Functions

A simplified mental model:

Regular function:

function () {}
Enter fullscreen mode Exit fullscreen mode

creates:

its own this
Enter fullscreen mode Exit fullscreen mode

Arrow function:

() => {}
Enter fullscreen mode Exit fullscreen mode

behaves conceptually more like:

const capturedThis = this;
Enter fullscreen mode Exit fullscreen mode

Then later:

use capturedThis forever
Enter fullscreen mode Exit fullscreen mode

Important:

Arrow functions do not dynamically recalculate this.

Ever.


13. Arrow Functions Ignore call, apply, and bind

This surprises many developers.

const user1 = { name: "John" };
const user2 = { name: "Alice" };

const arrow = () => {
    console.log(this.name);
};

arrow.call(user1);
arrow.call(user2);
Enter fullscreen mode Exit fullscreen mode

call does nothing.

Why?

Because arrow functions already captured this earlier.

They cannot be rebound.


14. Why Arrow Functions Were Added

Before arrow functions, developers constantly had problems with callbacks losing this.

Example:

const user = {
    name: "John",

    delayedHello: function () {
        setTimeout(function () {
            console.log(this.name);
        }, 1000);
    }
};

user.delayedHello();
Enter fullscreen mode Exit fullscreen mode

Output:

undefined
Enter fullscreen mode Exit fullscreen mode

because setTimeout calls the callback as a normal function.

Developers used ugly workarounds.


Old Solution #1: bind

setTimeout(function () {
    console.log(this.name);
}.bind(this), 1000);
Enter fullscreen mode Exit fullscreen mode

Old Solution #2: self = this

const self = this;

setTimeout(function () {
    console.log(self.name);
}, 1000);
Enter fullscreen mode Exit fullscreen mode

15. Arrow Functions Solve This Naturally

const user = {
    name: "John",

    delayedHello() {
        setTimeout(() => {
            console.log(this.name);
        }, 1000);
    }
};

user.delayedHello();
Enter fullscreen mode Exit fullscreen mode

Output:

John
Enter fullscreen mode Exit fullscreen mode

The arrow function captures this from delayedHello.


16. Arrow Functions and Closures

Arrow functions use the same mechanism as closures.

A closure remembers variables from outer scopes.

Arrow functions additionally remember:

  • this
  • arguments
  • super

from the surrounding context.


17. Arrow Functions Do NOT Have Their Own:

  • this
  • arguments
  • prototype
  • super
  • new.target

This leads to important limitations.


18. Arrow Functions Cannot Be Constructors

This fails:

const Person = (name) => {
    this.name = name;
};

new Person("John");
Enter fullscreen mode Exit fullscreen mode

Error:

Person is not a constructor
Enter fullscreen mode Exit fullscreen mode

Because arrow functions have no constructor behavior.


19. Arrow Functions as Object Methods: Dangerous

Bad example:

const user = {
    name: "John",

    sayHello: () => {
        console.log(this.name);
    }
};

user.sayHello();
Enter fullscreen mode Exit fullscreen mode

Output:

undefined
Enter fullscreen mode Exit fullscreen mode

Why?

Because object literals do NOT create a new this scope.

The arrow function is created in the surrounding lexical environment.

Usually that means:

this === globalThis
Enter fullscreen mode Exit fullscreen mode

or:

this === undefined
Enter fullscreen mode Exit fullscreen mode

in modules and strict mode.

So the arrow function captures the outer this, not the object.

This is one of the biggest beginner mistakes.


20. When Arrow Functions SHOULD Be Used

Arrow functions are excellent for:

  • callbacks,
  • asynchronous code,
  • array methods,
  • preserving surrounding this.

Example:

items.map(item => item.id);
Enter fullscreen mode Exit fullscreen mode

or:

button.addEventListener("click", () => {
    this.handleClick();
});
Enter fullscreen mode Exit fullscreen mode

21. When Regular Functions SHOULD Be Used

Regular functions are better when:

  • you need dynamic this,
  • you need object methods,
  • you use constructors,
  • you need arguments,
  • you want reusable method behavior.

Example:

const user = {
    name: "John",

    sayHello() {
        console.log(this.name);
    }
};
Enter fullscreen mode Exit fullscreen mode

22. Classes and this

Classes in JavaScript introduce an important distinction between:

  1. regular class methods,
  2. class fields.

Understanding this difference is critical for understanding how this behaves inside classes.


23. Regular Class Methods

Example:

class User {
    constructor(name) {
        this.name = name;
    }

    sayHello() {
        console.log(this.name);
    }
}
Enter fullscreen mode Exit fullscreen mode

This method:

sayHello() {}
Enter fullscreen mode Exit fullscreen mode

is NOT created inside the constructor.

Instead, JavaScript roughly transforms the class into:

function User(name) {
    this.name = name;
}

User.prototype.sayHello = function () {
    console.log(this.name);
};
Enter fullscreen mode Exit fullscreen mode

Important consequences:

  • the method lives on the prototype,
  • only one copy exists,
  • this is determined dynamically at call time,
  • the method can lose this when passed around.

24. Losing this in Class Methods

Example:

class User {
    constructor(name) {
        this.name = name;
    }

    sayHello() {
        console.log(this.name);
    }
}

const user = new User("John");

const fn = user.sayHello;

fn();
Enter fullscreen mode Exit fullscreen mode

Output in strict mode:

undefined
Enter fullscreen mode Exit fullscreen mode

Why?

Because:

fn()
Enter fullscreen mode Exit fullscreen mode

is a normal function call.

The method is no longer called through:

user.sayHello()
Enter fullscreen mode Exit fullscreen mode

Therefore this is lost.

This behavior is identical to regular object methods.


25. Class Fields Behave Differently

Now consider:

class User {
    name = "John";

    sayHello = () => {
        console.log(this.name);
    };
}
Enter fullscreen mode Exit fullscreen mode

This looks similar to a regular method, but internally it works very differently.

Class fields are initialized for every instance during constructor execution.

JavaScript roughly transforms the code into:

class User {
    constructor() {
        this.name = "John";

        this.sayHello = () => {
            console.log(this.name);
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

This is the key difference.


26. Why Arrow Functions Work in Class Fields

When this runs:

const user = new User();
Enter fullscreen mode Exit fullscreen mode

JavaScript creates a new instance and enters the constructor.

Inside the constructor:

this === newly created instance
Enter fullscreen mode Exit fullscreen mode

Then JavaScript executes:

this.sayHello = () => {
    console.log(this.name);
};
Enter fullscreen mode Exit fullscreen mode

At this exact moment, the arrow function captures the constructor's this.

And constructor this is the instance.

Conceptually:

const capturedThis = instance;
Enter fullscreen mode Exit fullscreen mode

The arrow function permanently remembers that value.

Therefore later:

user.sayHello();
Enter fullscreen mode Exit fullscreen mode

correctly prints:

John
Enter fullscreen mode Exit fullscreen mode

27. The Most Important Distinction

This is NOT because:

“Classes magically bind arrow functions to the instance.”

And NOT because:

“Classes create all methods inside the constructor.”

The real reason is:

Class fields are initialized during constructor execution.

If a class field contains an arrow function, that arrow function captures constructor this.

And constructor this is the instance.


28. Performance Trade-offs

Regular Prototype Methods

class User {
    sayHello() {}
}
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • one shared function,
  • lower memory usage,
  • faster instance creation,
  • better for large numbers of objects.

Disadvantages:

  • this can be lost,
  • often requires bind in callbacks.

Arrow Function Class Fields

class User {
    sayHello = () => {}
}
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • automatically preserves instance this,
  • excellent for callbacks and event handlers,
  • no need for manual bind.

Disadvantages:

  • every instance gets a new function,
  • higher memory usage,
  • methods are not shared through prototype,
  • slightly slower object creation.

29. Final Mental Model for Classes

Regular Class Method

sayHello() {}
Enter fullscreen mode Exit fullscreen mode

Think:

“Prototype method with dynamic this.”


Arrow Function Class Field

sayHello = () => {}
Enter fullscreen mode Exit fullscreen mode

Think:

“Constructor assignment that lexically captures instance this.”

That single distinction explains almost all class-related this behavior in JavaScript.


30. The Ultimate Mental Model

Regular Functions

Think:

this depends on HOW the function is called.”


Arrow Functions

Think:

this is permanently copied from the surrounding scope.”

Not dynamically determined.
Not changeable later.


31. The Most Important Difference

Regular function:

function () {}
Enter fullscreen mode Exit fullscreen mode

asks:

“Who called me?”

Arrow function:

() => {}
Enter fullscreen mode Exit fullscreen mode

asks:

“Where was I created?”

That single difference explains almost all behavior.


32. Best Practices

Use Arrow Functions For

  • callbacks,
  • promises,
  • timers,
  • event handlers needing outer this,
  • functional programming,
  • short utility functions.

Use Regular Functions For

  • object methods,
  • prototypes,
  • constructors,
  • dynamic context,
  • reusable APIs.

33. Common Interview Question

What is the difference between:

function () {}
Enter fullscreen mode Exit fullscreen mode

and

() => {}
Enter fullscreen mode Exit fullscreen mode

Correct deep answer:

Regular functions get this dynamically at call time.

Arrow functions lexically capture this from the surrounding scope when created.

Everything else is a consequence of that rule.


34. Final Summary

Regular Functions

  • Have their own this
  • this determined by call-site
  • Can be rebound
  • Can be constructors
  • Better for methods

Arrow Functions

  • No own this
  • Capture outer this
  • Cannot be rebound
  • Cannot be constructors
  • Better for callbacks

35. Final Thought

Most confusion about this disappears once you stop thinking of arrow functions as “shorter functions”.

They are not just syntax sugar.

They are a fundamentally different function model.

Regular functions are dynamic.

Arrow functions are lexical.

Understanding this distinction is the key to mastering JavaScript.

Top comments (0)