Skip to main content

Command Palette

Search for a command to run...

Understanding this in JavaScript (The Right Way)

Updated
β€’9 min readβ€’View as Markdown
Understanding this in JavaScript (The Right Way)

If there's one concept in JavaScript that confuses almost everyone, it's this. The confusion usually comes from memorizing scattered rules instead of understanding one core idea.

Core idea

this simply represents: β€œWho is calling this function?”

  • Not where the function is written.

  • Not how it is defined.

  • Only who is calling it.

Once this clicks, everything becomes much easier.

this in the global / top-level context

When this is used outside any function or object, it refers to the global environment β€” but with important environment nuances:

  • Browser (classic scripts): top-level this is window.

    console.log(this); // window
    
  • Node.js (CommonJS modules): top-level this is module.exports (an empty object by default).

    console.log(this); // {}
    
  • ES modules (browser and Node): top-level this is undefined.

  • In strict mode, a plain function called without a caller yields this === undefined. In non-strict mode it falls back to the global object.

Think of it like: β€œNo one called me, so I belong to the world.”

Example:

var name = "Bharat";
console.log(this.name); // In browsers (script mode) -> "Bharat"

this inside objects (implicit binding)

When a function is called as a property of an object (object.method()), this refers to that object.

const user = {
  name: "Bharat",
  greet: function() {
    console.log("Hello, I am " + this.name);
  }
};

user.greet(); // "Hello, I am Bharat"

Caller: user.greet() β†’ this = user.

Key insight: this depends on who calls the function, not where it's defined.

this inside plain function calls (default binding)

When a function is invoked normally (not as an object property):

function sayHello() {
  console.log(this);
}
sayHello();
  • Non-strict: this β†’ global object (browser: window).

  • Strict mode: this β†’ undefined.

Example:

function printName() {
  console.log(this.name);
}
var name = "Global";
printName(); // "Global" in browsers (non-strict)

How calling context changes this (most important)

One function, different callers β†’ different this.

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

var name = "Global";
show(); // default binding -> global (or undefined in strict)

const obj = { name: "Object", show: show };
obj.show(); // implicit binding -> this = obj

const obj2 = { name: "Second Object" };
obj2.show = obj.show;
obj2.show(); // this = obj2

Same function β€” different caller β€” different this.

Broken example (lost this)

Real-life: a restaurant object with a method. If you extract the method, this is lost.

const restaurant = {
  name: "Spice Hub",
  menu: ["Biryani", "Paneer", "Naan"],
  showMenu: function() {
    console.log("Welcome to " + this.name);
    console.log("Menu:", this.menu);
  }
};

restaurant.showMenu();
// Welcome to Spice Hub
// Menu: [ "Biryani", "Paneer", "Naan" ]

const fn = restaurant.showMenu;
fn();
// In non-strict environments -> "Welcome to undefined" or "Menu: undefined"
// In strict mode -> TypeError or this === undefined leading to error

Why it broke: fn() is a plain function call, so this is not restaurant anymore.

Fixes / Solutions

  • Call through the object:

    restaurant.showMenu();
    
  • Use bind to permanently bind this:

    const fn = restaurant.showMenu.bind(restaurant);
    fn(); // works
    
  • Use call / apply to invoke with explicit this:

    restaurant.showMenu.call(restaurant);
    restaurant.showMenu.apply(restaurant);
    
  • Use a wrapper to preserve the object:

    const fn = () => restaurant.showMenu();
    fn();
    

    (A simple, often-clean solution β€” but note it closes over restaurant by reference.)

  • For class-based code, bind methods in the constructor or use class fields to create bound functions:

    class Foo {
      constructor() {
        this.method = this.method.bind(this);
      }
      method() { ... }
    }
    

    or

    class Foo {
      method = () => { ... } // class field arrow -> lexical this
    }
    

Arrow functions and this

  • Arrow functions do not have their own this. They capture this lexically from the surrounding scope.

  • Useful for callbacks where you want the outer this.

  • Not suitable as object methods if you expect this to be the object (because the lexical this will likely be the surrounding scope, not the object).

Precedence of bindings (short)

  1. new binding (constructor) β€” highest precedence.

  2. explicit binding (call, apply, bind).

  3. implicit binding (object property call).

  4. default binding (global or undefined) β€” lowest precedence.

(As a nuance: new can override bind β€” when you new a bound function, the newly created instance becomes this.)

Best practices

  • Keep the core idea in mind: this is about who calls the function.

  • Prefer explicit binding (bind, call) or wrapper functions when you need a method detached from its object.

  • Use arrow functions for inner callbacks to capture outer this.

  • In modern code, classes with properly bound methods or arrow class fields avoid many pitfalls.

  • Use strict mode to avoid accidental global this (and unpredictable behavior).

Quick summary

  • this = the object that calls the function.

  • If no caller: default to global (non-strict) or undefined (strict / ES modules).

  • Use explicit binding or wrappers to preserve this when extracting methods.

⚑ Arrow Functions & this (MOST IMPORTANT)

Arrow functions behave completely differently.

πŸ‘‰ Arrow functions do NOT have their own this
πŸ‘‰ They borrow this from where they are written


πŸ“˜ Definition

Arrow function this = lexical this
πŸ‘‰ Meaning: it comes from the surrounding scope


πŸ”₯ Example 1 (Normal vs Arrow)

const user = {
    name: "Bharat",

    normalFn: function() {
        console.log("Normal:", this.name)
    },

    arrowFn: () => {
        console.log("Arrow:", this.name)
    }
}

user.normalFn()
user.arrowFn()

Output:

Normal: Bharat
Arrow: undefined

🧠 Why?

  • normalFn β†’ called by user β†’ this = user

  • arrowFn β†’ no own this β†’ takes from outer (global)


πŸ”₯ Example 2 (Where Arrow is Useful)

const user = {
    name: "Bharat",

    greet: function() {
        setTimeout(() => {
            console.log("Hello " + this.name)
        }, 1000)
    }
}

user.greet()

🧠 Why arrow works here?

  • setTimeout runs later

  • Normal function would lose this

  • Arrow keeps this = user


❌ Wrong version:

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

πŸ‘‰ this = global β†’ ❌


πŸ”₯ Rule:

Normal function β†’ this depends on caller  
Arrow function β†’ this depends on where it is written

πŸ”— call(), apply(), bind() (Control this Manually)

Now we take control.


πŸ“˜ Definition

These methods let you manually set what this should be


🟒 1. call()

πŸ‘‰ Calls function immediately

πŸ‘‰ Pass arguments one by one


Example:

function greet() {
    console.log("Hello " + this.name)
}

const user1 = { name: "Bharat" }
const user2 = { name: "Aman" }

greet.call(user1)
greet.call(user2)

Output:

Hello Bharat
Hello Aman

πŸ”΅ 2. apply()

πŸ‘‰ Same as call

πŸ‘‰ But arguments passed as array


Example:

function introduce(age, city) {
    console.log(this.name, age, city)
}

const user = { name: "Bharat" }

introduce.apply(user, [20, "Delhi"])

Output:

Bharat 20 Delhi

🟑 3. bind()

πŸ‘‰ Does NOT call function immediately

πŸ‘‰ Returns a new function


Example:

function greet() {
    console.log("Hello " + this.name)
}

const user = { name: "Bharat" }

const newFn = greet.bind(user)

newFn()

Output:

Hello Bharat

πŸ”₯ Real Difference

Method Runs Immediately Arguments Style
call βœ… Yes comma separated
apply βœ… Yes array
bind ❌ No returns function

⚠️ Losing this (Real Bug You WILL Face)

This is where most devs struggle.


❌ Problem Example

const user = {
    name: "Bharat",
    greet() {
        console.log(this.name)
    }
}

const fn = user.greet
fn()

Output:

undefined

🧠 Why?

πŸ‘‰ Function lost its caller

πŸ‘‰ Now called as normal function

πŸ‘‰ this = global


Fix 1: Use bind

const fn = user.greet.bind(user)
fn()

Fix 2: Use arrow (if inside)

const user = {
    name: "Bharat",
    greet() {
        const inner = () => {
            console.log(this.name)
        }
        inner()
    }
}

Real-World Scenario

Example: Button Click

const button = {
    text: "Click me",
    handleClick() {
        console.log(this.text)
    }
}

Problem:

setTimeout(button.handleClick, 1000)

πŸ‘‰ this lost ❌


Fix:

setTimeout(button.handleClick.bind(button), 1000)

Visual Diagram


Normal Function

user.greet()

user ─────► greet()
            this = user

Lost Reference

fn = user.greet
fn()

global ─────► greet()
              this = global

Arrow Function

outer scope ─────► arrow function
                   this = outer this

call/apply/bind

greet.call(user)

user ─────► greet()
            this = user (forced)

Final Mental Model (Advanced)


Step 1:

Is it arrow function?

  • YES β†’ take this from outer

  • NO β†’ go to step 2


Step 2:

Who is calling function?

  • object β†’ this = object

  • normal call β†’ this = global


Step 3:

Is call/apply/bind used?

  • YES β†’ this = manually set

  • NO β†’ default rules apply


Final Summary

  • Arrow functions don’t have this

  • this can be controlled using call/apply/bind

  • Losing this is common bug

  • bind is most used fix

  • setTimeout + callbacks β†’ dangerous zone