JavaScript this Binding in Standard Function Calls

In JavaScript, the value of the this keyword inside standard functions is determined dynamically at runtime based on how and where the function is executed, rather than where it is defined. Understanding this binding comes down to evaluating the execution context through four primary rules: default binding, implicit binding, explicit binding, and constructor binding. This guide breaks down each mechanism to clarify how standard function calls assign their execution context.

1. Default Binding (Standalone Function Calls)

When a standard function is invoked on its own without any qualifying context, JavaScript uses default binding.

function showContext() {
  console.log(this);
}

showContext(); // Logs: window (in browsers) / global (in Node)

function showStrictContext() {
  'use strict';
  console.log(this);
}

showStrictContext(); // Logs: undefined

2. Implicit Binding (Method Invocations)

When a function is invoked as a method belonging to an object (using dot notation or bracket notation), this implicitly binds to the immediate object containing the call site.

const user = {
  name: 'Alex',
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

user.greet(); // Logs: "Hello, my name is Alex"

If multiple nested objects are chained together, this binds to the object directly preceding the method invocation:

const company = {
  department: {
    name: 'Engineering',
    getName() {
      return this.name;
    }
  }
};

console.log(company.department.getName()); // Logs: "Engineering"

The Pitfall of Losing Implicit Context

Assigning an object method to a standard variable or passing it as a callback isolates the function from its parent object. When executed, it falls back to default binding:

const standaloneGreet = user.greet;
standaloneGreet(); // Logs: "Hello, my name is undefined" (or errors in strict mode)

3. Explicit Binding (call, apply, and bind)

JavaScript provides built-in prototype methods that allow you to explicitly dictate what this should reference during execution.

function introduce(greeting, punctuation) {
  console.log(`${greeting}, I am ${this.name}${punctuation}`);
}

const person = { name: 'Sarah' };

// Using call and apply
introduce.call(person, 'Hello', '.'); // Logs: "Hello, I am Sarah."
introduce.apply(person, ['Hi', '!']); // Logs: "Hi, I am Sarah!"

// Using bind
const boundIntroduce = introduce.bind(person, 'Greetings');
boundIntroduce('?'); // Logs: "Greetings, I am Sarah?"

4. Constructor Binding (new Operator)

When a standard function is invoked with the new keyword as a constructor, a series of steps occur automatically:

  1. A new, empty object is created.
  2. The newly created object’s prototype is linked to the function’s prototype property.
  3. The function executes with this bound directly to the new object.
  4. The function returns the newly created object (unless an alternate object is explicitly returned).
function Car(make, model) {
  this.make = make;
  this.model = model;
}

const myCar = new Car('Toyota', 'Corolla');
console.log(myCar.make); // Logs: "Toyota"

Order of Precedence

When multiple rules appear to conflict, JavaScript resolves this using a strict hierarchy of precedence:

  1. new Keyword: this refers to the newly instantiated object.
  2. Explicit Binding (bind, call, apply): this refers to the explicitly provided object.
  3. Implicit Binding (Object Methods): this refers to the context object before the dot.
  4. Default Binding: this refers to the global object or undefined in strict mode.