DEV Community

Maxim Logunov
Maxim Logunov

Posted 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,
  • why classes behave differently,
  • 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 the arrow function does NOT get:

this === user
Enter fullscreen mode Exit fullscreen mode

Instead, it captures this from the outer scope.

Usually global scope.

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

In classes, methods are regular functions.

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

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

But class methods often lose this in callbacks.


23. Common React Example

class App {
    constructor() {
        this.count = 0;
    }

    increment() {
        this.count++;
    }
}
Enter fullscreen mode Exit fullscreen mode

Problem:

button.addEventListener("click", app.increment);
Enter fullscreen mode Exit fullscreen mode

this is lost.

Solution:

this.increment = this.increment.bind(this);
Enter fullscreen mode Exit fullscreen mode

OR:

increment = () => {
    this.count++;
}
Enter fullscreen mode Exit fullscreen mode

The arrow function captures the instance this.


24. Performance Considerations

Regular Methods

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

Method stored once on prototype.

Efficient memory usage.


Arrow Methods in Classes

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

New function created per instance.

More memory usage.

But easier this handling.

Trade-off:

Feature Regular Method Arrow Method
Dynamic this Yes No
Prototype sharing Yes No
Easier callbacks No Yes
Memory efficient Yes Less
Can use new Yes No

25. 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.


26. 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.


27. 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.

28. 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.


29. 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

30. 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)