The this Keyword

Difficulty: Intermediate

The `this` keyword in JavaScript is one of the most confusing aspects of the language because, unlike most other languages where `this` always refers to the current instance, JavaScript's `this` is determined dynamically at call time based on how a function is invoked. The same function can have different `this` values depending on the calling context. This is fundamentally different from lexical scoping where the context is determined by where code is written.

There are four main rules that determine what `this` refers to, evaluated in order of precedence. The first is default binding: when a function is called as a standalone function (not as a method, not with new, and not with call/apply/bind), `this` defaults to the global object (window in browsers, global in Node.js). In strict mode, the default binding sets `this` to undefined instead, which helps catch accidental global variable creation.

The second rule is implicit binding: when a function is called as a method of an object (using dot notation like obj.method()), `this` is set to the object that owns the method. However, this binding is easily lost. If you extract the method from the object and call it standalone, or pass it as a callback, the implicit binding is lost and default binding takes over. This is one of the most common sources of bugs involving `this`.

The third rule is explicit binding using call(), apply(), or bind(). These methods let you manually specify what `this` should be. call and apply invoke the function immediately with the specified `this`, while bind returns a new function with `this` permanently set. The fourth rule is the `new` binding: when a function is called with the `new` keyword, `this` is set to the newly created object. This is how constructor functions and classes work.

Arrow functions introduced in ES6 are a special case that deliberately break from these rules. Arrow functions do not have their own `this` binding. Instead, they inherit `this` from their enclosing lexical scope at the time they are defined. This makes them ideal for callbacks and event handlers where you want to preserve the `this` value of the surrounding code. However, it also means arrow functions should not be used as methods on objects or as constructors.

In event handlers in the browser, `this` refers to the DOM element that the event listener is attached to (when using regular functions). This is an example of implicit binding -- the browser calls the handler as a method of the element. If you need to access a different `this` inside an event handler (for example, a class instance), you can use an arrow function or bind the handler in advance.

Code examples

The Four Binding Rules

// 1. Default Binding
function showThis() {
  'use strict';
  console.log('Default:', this);
}
showThis(); // undefined (strict mode)

// 2. Implicit Binding
const user = {
  name: 'Alice',
  greet() {
    console.log('Implicit:', this.name);
  }
};
user.greet(); // 'Alice'

// 3. Explicit Binding
function introduce(greeting) {
  console.log(`${greeting}, I'm ${this.name}`);
}
const bob = { name: 'Bob' };
introduce.call(bob, 'Hello'); // Explicit via call

// 4. new Binding
function Person(name) {
  this.name = name;
}
const charlie = new Person('Charlie');
console.log('new:', charlie.name);

Each binding rule produces a different `this` value. Default binding gives undefined (strict mode) or global object. Implicit binding uses the calling object. Explicit binding lets you choose. The new keyword creates a fresh object.

Losing Implicit Binding

const calculator = {
  value: 100,
  getValue() {
    return this.value;
  }
};

// Works -- implicit binding
console.log(calculator.getValue()); // 100

// Broken -- binding is lost when method is extracted
const getValue = calculator.getValue;
console.log(getValue()); // undefined

// Broken -- binding is lost when passed as callback
function executeCallback(cb) {
  return cb();
}
console.log(executeCallback(calculator.getValue)); // undefined

// Fix with bind
const boundGetValue = calculator.getValue.bind(calculator);
console.log(boundGetValue()); // 100
console.log(executeCallback(boundGetValue)); // 100

When you assign a method to a variable or pass it as a callback, the implicit binding to the object is lost. The function is now called as a standalone function, so `this` falls back to default binding (undefined in strict mode). Using bind() creates a new function with `this` permanently set.

Arrow Functions and this

const team = {
  name: 'Engineering',
  members: ['Alice', 'Bob', 'Charlie'],

  // Regular function -- `this` is the team object
  listMembersRegular() {
    console.log(`Team: ${this.name}`);
    // Regular function callback -- `this` is lost!
    this.members.forEach(function(member) {
      // `this` is undefined here (strict) or global
      console.log(`  ${member} -> ${this?.name || 'LOST'}`);
    });
  },

  // Arrow function fixes it
  listMembersArrow() {
    console.log(`Team: ${this.name}`);
    // Arrow function inherits `this` from listMembersArrow
    this.members.forEach((member) => {
      console.log(`  ${member} -> ${this.name}`);
    });
  }
};

team.listMembersRegular();
console.log('---');
team.listMembersArrow();

In listMembersRegular, the forEach callback is a regular function, so it gets its own `this` (default binding). In listMembersArrow, the forEach callback is an arrow function that inherits `this` from the enclosing method, preserving access to the team object.

this Precedence and Common Pitfalls

// Precedence: new > explicit > implicit > default
function Foo(name) {
  this.name = name;
}

const obj1 = { name: 'obj1' };

// bind sets this to obj1
const BoundFoo = Foo.bind(obj1);
BoundFoo('bound-call');
console.log(obj1.name); // 'bound-call'

// new overrides bind!
const obj2 = new BoundFoo('new-call');
console.log(obj2.name); // 'new-call' (new wins)
console.log(obj1.name); // 'bound-call' (unchanged)

// Pitfall: arrow function as object method
const badObj = {
  value: 42,
  getValue: () => {
    // `this` is NOT badObj -- it's the outer scope!
    return this?.value;
  }
};
console.log(badObj.getValue()); // undefined

The `new` keyword has the highest precedence and overrides even bind(). Arrow functions used as object methods are a common mistake -- they inherit `this` from where the object literal is evaluated, not from the object itself.

Key points

Concepts covered

Default Binding, Implicit Binding, Explicit Binding, new Binding, Arrow Functions, this in Event Handlers