The JavaScript this Keyword: Full Deep Dive

A complete deep dive into JavaScript's this keyword. Understand how this is determined by call site, not by where code is written. Covers global context, method calls, explicit binding, new binding, class methods, and the rules for determining this in every situation.

JavaScriptintermediate
13 min read

this is one of the most commonly misunderstood mechanisms in JavaScript. Unlike most languages where this refers to the current object instance at definition time, JavaScript's this is determined entirely at call time — by how a function is invoked, not where it is written. Once you understand the four binding rules, this becomes completely predictable.

The Golden Rule: this Is Set at Call Time

javascriptjavascript
function whoAmI() {
  console.log(this);
}
 
const obj = { whoAmI };
 
whoAmI();      // globalThis (or undefined in strict mode)
obj.whoAmI();  // obj — same function, different this

Same function. Two different call sites. Two different this values. The call site determines this.

Rule 1: Default Binding

When a function is called as a plain function (no receiver, no new, no explicit binding), this gets the default binding:

javascriptjavascript
function showThis() {
  console.log(this);
}
 
showThis(); // window (browser) / global (Node.js)

In strict mode ("use strict" or inside a class/module), the default binding is undefined:

javascriptjavascript
"use strict";
function strictThis() {
  console.log(this); // undefined
}
strictThis();

Rule 2: Implicit Binding

When a function is called as a method on an object, this is the object to the left of the dot:

javascriptjavascript
const person = {
  name: "Alice",
  greet() {
    return `Hi, I'm ${this.name}`;
  },
};
 
console.log(person.greet()); // "Hi, I'm Alice" — this = person

The binding is always the immediate caller — the last object in the chain:

javascriptjavascript
const outer = {
  name: "Outer",
  inner: {
    name: "Inner",
    greet() { return this.name; },
  },
};
 
console.log(outer.inner.greet()); // "Inner" — this = outer.inner, not outer

Implicit Binding Loss

The binding is lost when the method reference is detached from its object:

javascriptjavascript
const user = {
  name: "Bob",
  sayName() { return this.name; },
};
 
const fn = user.sayName; // Reference copied — detached from user
fn(); // undefined (strict mode) — THIS IS THE INFAMOUS CONTEXT LOSS

This is the source of most this-related bugs. See losing this in JavaScript callbacks for a full treatment.

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

You can explicitly set this using call(), apply(), and bind():

javascriptjavascript
function introduce(greeting, punctuation) {
  return `${greeting}, I'm ${this.name}${punctuation}`;
}
 
const alice = { name: "Alice" };
const bob   = { name: "Bob"   };
 
// call: arguments one by one
introduce.call(alice, "Hello", "!"); // "Hello, I'm Alice!"
introduce.call(bob,   "Hey",   ".");  // "Hey, I'm Bob."
 
// apply: arguments as array
introduce.apply(alice, ["Hi", "?"]); // "Hi, I'm Alice?"
 
// bind: returns a new function with this permanently bound
const aliceIntro = introduce.bind(alice);
aliceIntro("Greetings", "!"); // "Greetings, I'm Alice!"

For a complete guide to these three methods, see js bind, call, and apply methods full tutorial.

Rule 4: new Binding

When a function is called with new, this is the newly created object:

javascriptjavascript
function Timer(label) {
  this.label = label;
  this.elapsed = 0;
  // this = the fresh object created by new
}
Timer.prototype.tick = function() { this.elapsed += 1; };
 
const t = new Timer("stopwatch");
console.log(t.label);   // "stopwatch"
console.log(t.elapsed); // 0

The new binding overrides all other rules except arrow functions.

Rule Priority

When multiple binding rules could apply, the priority is:

PriorityRuleExample
1 (highest)new bindingnew Foo()
2Explicit bindingfoo.call(ctx) / foo.bind(ctx)()
3Implicit bindingobj.foo()
4 (lowest)Default bindingfoo()
javascriptjavascript
function greet() { return this.name; }
const obj = { name: "A", greet };
 
// Implicit says this = obj (rule 3)
// But explicit overrides:
greet.call({ name: "B" }); // "B" — explicit wins over implicit

this in Arrow Functions

Arrow functions are the exception to all four rules — they capture this from their enclosing lexical scope at definition time and cannot be rebound:

javascriptjavascript
const team = {
  name: "DevTeam",
  members: ["Alice", "Bob"],
  listMembers() {
    // Arrow function captures 'this' from listMembers() — which is team
    return this.members.map(m => `${this.name}: ${m}`);
  },
};
 
console.log(team.listMembers());
// ["DevTeam: Alice", "DevTeam: Bob"]
 
// Calling bind/call/apply on an arrow function has no effect on this
const arrow = () => this;
arrow.call({ x: 1 }); // Still the original captured this

call, apply, and bind cannot change this for arrow functions. For the full story, see how arrow functions change this in JavaScript.

this in Class Methods

Inside a class, this behaves as implicit binding — it refers to the instance when called as a method:

javascriptjavascript
class Counter {
  count = 0;
 
  increment() {
    this.count += 1; // this = the Counter instance
    return this;
  }
 
  decrement() {
    this.count -= 1;
    return this;
  }
}
 
const c = new Counter();
c.increment().increment().decrement();
console.log(c.count); // 1

But class methods still lose this when extracted as callbacks:

javascriptjavascript
const c2 = new Counter();
const inc = c2.increment; // Detached!
// inc() → TypeError: Cannot set properties of undefined
// Fix: use bind, arrow field, or arrow wrapper

this in Event Handlers and Callbacks

The most common pain point — this in callbacks:

javascriptjavascript
class Button {
  constructor(label) {
    this.label = label;
    this.clicks = 0;
  }
 
  // BAD: this will be undefined (strict) or the event target (DOM)
  handleClickBad() {
    this.clicks++;
  }
 
  // GOOD option 1: bind in constructor
  constructor2(label) {
    this.label = label;
    this.clicks = 0;
    this.handleClick = this.handleClick.bind(this);
  }
 
  // GOOD option 2: class field arrow function
  handleClick = () => {
    this.clicks++; // Arrow captures this from class body (the instance)
    console.log(`${this.label} clicked ${this.clicks} times`);
  };
}
 
const btn = new Button("Submit");
setTimeout(btn.handleClick, 100); // Works: arrow field keeps this

Quick Reference: this by Call Pattern

javascriptjavascript
function fn() { return this; }
const obj = { fn };
 
fn();                    // globalThis or undefined (strict)
obj.fn();                // obj
fn.call({ x: 1 });       // { x: 1 }
fn.apply({ x: 2 });      // { x: 2 }
const bound = fn.bind({ x: 3 }); bound(); // { x: 3 }
new fn();                // {} (new object)
const arrow = () => this; // Lexical — whatever this was at definition
Rune AI

Rune AI

Key Insights

  • this is set at call time, not definition time: The same function can have different this values depending on how it is invoked — which object is to the left of the dot or which binding method is used
  • Four binding rules in priority order: new > explicit (call/apply/bind) > implicit (obj.method()) > default (bare call)
  • Implicit binding is easily lost: Assigning a method to a variable or passing it as a callback detaches it from its object — this becomes undefined (strict) or globalThis
  • Arrow functions capture this lexically: Defined at creation time from the enclosing scope; cannot be overridden by call, apply, or bind
  • Class field arrow functions solve callback binding: Declaring methods as arrow field properties (handleClick = () => { ... }) ensures this is always the instance
RunePowered by Rune AI

Frequently Asked Questions

Why does this work differently from other languages?

In Java or C++, `this` always refers to the current object. JavaScript is a multi-paradigm language — functions are first-class objects that can be reused across contexts. The dynamic `this` binding enables powerful patterns like method borrowing (`Array.prototype.slice.call(arrayLike)`), but at the cost of predictability when binding is lost.

How do I pass this safely to an async operation?

Use arrow functions (they capture `this` lexically) or bind the method: `setTimeout(this.method.bind(this), 1000)`. Alternatively, capture `this` in a variable (`const self = this`) before the callback — though this pattern is now largely replaced by arrow functions.

Does this work the same in Node.js modules?

In a Node.js CommonJS module, the top-level `this` is `module.exports` (an empty object), not `global`. In ES modules, the top-level `this` is `undefined`.

Can I reliably determine this at read time?

No — `this` is a runtime value. You can only determine it by tracing the call site. This is why modern code uses arrow functions and `bind` to make `this` explicit and predictable.

Conclusion

JavaScript this is determined by four rules, applied in priority order: new binding overrides everything; explicit binding (call/apply/bind) comes next; implicit binding (method call on an object) comes third; and default binding (bare function call) has the lowest priority. Arrow functions bypass all four rules — they capture this from their enclosing lexical scope at definition time. Mastering these rules eliminates the most common category of JavaScript bugs.