JavaScriptMedium

How does the 'this' keyword behave in JavaScript?

01

The Short Answer

The value of this in JavaScript is determined by how a function is called, not where it's defined. It can refer to the global object, the object that called the method, a manually bound object, or a newly created instance — depending on the invocation pattern. Arrow functions are the exception: they inherit this from their enclosing lexical scope and never get their own.

02

The Five Rules of this

JavaScript determines this at call time using a set of rules with a clear priority order. Understanding these rules lets you predict this in any situation.

Rule 1: Default binding (standalone function call)

When a function is called without any object context — just fn()this defaults to the global object (window in browsers, global in Node.js). In strict mode, it's undefined instead. This is the fallback when no other rule applies.

default-binding.tstypescript
function showThis() {
  console.log(this);
}

showThis(); // window (browser) or global (Node) — in sloppy mode
// In strict mode ('use strict'): undefined

// Common trap: extracting a method loses its context
const user = {
  name: 'Alice',
  greet() { console.log(this.name); }
};

const greet = user.greet; // Extracted — no longer called on user
greet(); // undefined (strict) or '' (window.name) — NOT 'Alice'

Rule 2: Implicit binding (method call)

When a function is called as a method on an object (obj.method()), this refers to the object to the left of the dot. The object "owns" the call, so it becomes the context. This is the most intuitive behavior — the object calling the method is this.

implicit-binding.tstypescript
const user = {
  name: 'Alice',
  greet() {
    console.log(`Hi, I'm ${this.name}`);
  },
  address: {
    city: 'NYC',
    getCity() {
      console.log(this.city);
    }
  }
};

user.greet();           // 'Hi, I'm Alice' — this = user
user.address.getCity(); // 'NYC' — this = user.address (nearest object)

// Only the IMMEDIATE caller matters
const getCity = user.address.getCity;
getCity(); // undefined — implicit binding lost (default binding applies)

Rule 3: Explicit binding (call, apply, bind)

You can manually set this using call(), apply(), or bind(). call and apply invoke the function immediately with the specified this value. bind returns a new function permanently bound to the specified this — it can never be overridden, even by another bind or call.

explicit-binding.tstypescript
function greet(greeting: string, punctuation: string) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const alice = { name: 'Alice' };
const bob = { name: 'Bob' };

// call — invokes immediately, args passed individually
greet.call(alice, 'Hi', '!');    // 'Hi, I'm Alice!'

// apply — invokes immediately, args passed as array
greet.apply(bob, ['Hey', '.']); // 'Hey, I'm Bob.'

// bind — returns a NEW function with this permanently set
const aliceGreet = greet.bind(alice);
aliceGreet('Hello', '~');        // 'Hello, I'm Alice~'

// bind is permanent — can't be overridden
aliceGreet.call(bob, 'Hi', '!'); // 'Hi, I'm Alice!' — still Alice!

Rule 4: new binding (constructor call)

When a function is called with new, JavaScript creates a fresh empty object, sets this to that new object, executes the function body (which typically adds properties to this), and returns the object. This is how constructor functions and classes create instances.

new-binding.tstypescript
function User(name: string, role: string) {
  // 'this' is a brand new empty object {}
  this.name = name;
  this.role = role;
  // implicitly returns 'this'
}

const admin = new User('Alice', 'admin');
console.log(admin.name); // 'Alice'
console.log(admin.role); // 'admin'

// What 'new' does under the hood:
// 1. const obj = {};
// 2. Object.setPrototypeOf(obj, User.prototype);
// 3. const result = User.call(obj, 'Alice', 'admin');
// 4. return (typeof result === 'object' && result) ? result : obj;

Rule 5: Arrow functions (lexical this)

Arrow functions don't have their own this. They capture this from the surrounding scope at the time they're defined — and that binding can never change. call, apply, bind, and new have no effect on an arrow function's this. This makes them perfect for callbacks where you want to preserve the outer context.

arrow-this.tstypescript
const user = {
  name: 'Alice',
  // Regular method — this = user (implicit binding)
  greetRegular() {
    setTimeout(function () {
      console.log(this.name); // undefined — 'this' is window/global
    }, 100);
  },
  // Arrow function captures 'this' from greetArrow's scope
  greetArrow() {
    setTimeout(() => {
      console.log(this.name); // 'Alice' — inherited from greetArrow
    }, 100);
  },
};

user.greetRegular(); // undefined (or window.name)
user.greetArrow();   // 'Alice' ✅

// Arrow functions can't be rebound
const arrowFn = () => console.log(this);
arrowFn.call({ name: 'Bob' }); // Still the outer 'this', not { name: 'Bob' }
03

Priority Order

When multiple rules could apply, JavaScript uses this priority (highest to lowest). The first matching rule wins.

  • Arrow function — always wins, `this` is lexically fixed at definition time
  • `new` binding — `this` is the newly created object
  • Explicit binding (`call`/`apply`/`bind`) — `this` is the specified object
  • Implicit binding (`obj.method()`) — `this` is the object before the dot
  • Default binding — `this` is global object (or `undefined` in strict mode)
04

this in Classes

In ES6 classes, methods follow the same rules. A method called on an instance uses implicit binding. But if you extract the method (pass it as a callback), the binding is lost. Class fields with arrow functions solve this by creating a per-instance function with lexical this.

class-this.tstypescript
class Counter {
  count = 0;

  // Regular method — 'this' depends on how it's called
  increment() {
    this.count++;
  }

  // Arrow function class field — 'this' is always the instance
  decrement = () => {
    this.count--;
  };
}

const counter = new Counter();

// Direct call — works fine (implicit binding)
counter.increment(); // this = counter ✅

// Extracted as callback — regular method loses 'this'
const inc = counter.increment;
inc(); // TypeError: Cannot read property 'count' of undefined 💥

// Arrow field — always bound to instance
const dec = counter.decrement;
dec(); // Works! this = counter ✅ (lexical binding)
05

this in Event Handlers

In DOM event handlers, this refers to the element that the listener is attached to. But in React, class component methods lose their binding when passed as callbacks (because React calls them without an object context). This is why React class components needed .bind(this) in the constructor or arrow function class fields.

event-handlers.tstypescript
// Vanilla DOM — 'this' is the element
const button = document.querySelector('button');
button.addEventListener('click', function () {
  console.log(this); // <button> element
});

// Arrow function — 'this' is the outer scope, NOT the element
button.addEventListener('click', () => {
  console.log(this); // window (or module scope) — NOT the button
});

// React class component — must bind or use arrow
class Form extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this); // Fix #1
  }

  handleSubmit() { /* this = component instance */ }
  handleClick = () => { /* this = component instance (arrow field) */ } // Fix #2
}
06

Common Mistakes

🔗

Losing this when passing methods as callbacks

Extracting a method from an object and passing it to setTimeout, addEventListener, or array methods strips the implicit binding. The function is called standalone, so `this` becomes undefined (strict) or global.

Use `.bind(this)`, wrap in an arrow function, or define the method as an arrow function class field.

➡️

Using arrow functions as object methods

Arrow functions on object literals capture `this` from the surrounding scope (usually module/global), not the object. `const obj = { name: 'A', greet: () => this.name }` — `this` is NOT `obj`.

Use regular function syntax for object methods: `greet() { return this.name; }`. Arrow functions are for callbacks inside methods, not for the methods themselves.

🏗️

Forgetting that this in nested functions isn't inherited

A regular function inside a method gets its own `this` (default binding), not the method's `this`. This trips people up with callbacks inside methods.

Use arrow functions for nested callbacks (they inherit `this`), or save `this` to a variable: `const self = this;`

07

Why Interviewers Ask This

this is one of the most confusing parts of JavaScript and a constant source of bugs. Interviewers ask this to verify you can predict this in any context, understand why methods lose their binding when extracted, know the difference between arrow and regular functions regarding this, and can debug this-related issues in real code. It's a litmus test for JavaScript fluency — if you truly understand this, you understand a core part of how the language works.

Quick Revision Cheat Sheet

Default: Standalone call → global (sloppy) or undefined (strict)

Implicit: obj.method() → this is the object before the dot

Explicit: call/apply/bind → this is the specified object

new: Constructor call → this is the newly created object

Arrow function: Inherits this from enclosing scope — can never be rebound

Priority: arrow > new > explicit > implicit > default