this Keyword & Binding Rules

Difficulty: Intermediate

Question

Explain how 'this' works in JavaScript. What are the binding rules and how do arrow functions differ?

Answer

The 'this' keyword in JavaScript is determined by how a function is called, not where it is defined. There are four binding rules in order of precedence:

1. new binding: When a function is called with 'new', this refers to the newly created object. 2. Explicit binding: call(), apply(), or bind() explicitly set this. 3. Implicit binding: When a function is called as a method of an object, this refers to that object. 4. Default binding: In non-strict mode, this defaults to the global object (window/global). In strict mode, this is undefined.

Arrow functions are a special case - they do not have their own 'this'. Instead, they inherit 'this' from the enclosing lexical scope at the time they are defined. This makes them ideal for callbacks and event handlers where you want to preserve the outer 'this'.

Code examples

The Four Binding Rules

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

// 2. Implicit binding
const obj = {
  name: 'Alice',
  greet() {
    console.log(`Hello, ${this.name}`);
  }
};
obj.greet(); // Hello, Alice

// 3. Explicit binding
const bob = { name: 'Bob' };
obj.greet.call(bob); // Hello, Bob

// 4. new binding
function Person(name) {
  this.name = name;
}
const p = new Person('Charlie');
console.log(p.name); // Charlie

Binding precedence: new > explicit (call/apply/bind) > implicit (method call) > default (standalone call).

Common 'this' Pitfall

const user = {
  name: 'Alice',
  friends: ['Bob', 'Charlie'],
  showFriends() {
    // 'this' is user here
    this.friends.forEach(function(friend) {
      // 'this' is undefined/window here (not user!)
      console.log(`${this?.name} knows ${friend}`);
    });
  },
  showFriendsFixed() {
    // Arrow function inherits 'this' from showFriendsFixed
    this.friends.forEach((friend) => {
      console.log(`${this.name} knows ${friend}`);
    });
  }
};

user.showFriends();
console.log('---');
user.showFriendsFixed();

Regular functions in callbacks lose the outer 'this'. Arrow functions solve this by inheriting from the enclosing scope.

Arrow Functions and this

const timer = {
  seconds: 0,
  // Arrow function - inherits 'this' from timer object context
  start() {
    // 'this' is timer here
    const tick = () => {
      this.seconds++; // 'this' is still timer
      console.log(this.seconds);
    };
    tick();
    tick();
  }
};
timer.start();

// Arrow functions cannot be used as constructors
const Foo = () => {};
try {
  new Foo();
} catch (e) {
  console.log(e.message);
}

// Arrow function 'this' cannot be overridden
const arrowFn = () => console.log(this);
arrowFn.call({ a: 1 }); // ignores call, uses lexical this

Arrow functions have no own 'this', no 'arguments', no 'prototype', and cannot be used with 'new'. Their 'this' is permanently set at creation time.

Key points

Concepts covered

this, Implicit Binding, Explicit Binding, Arrow Functions, Global Context