The Context System

In JavaScript, functions are objects and are treated as First Class Citizens.

this is a hidden parameter passed to every function that points to the Execution Context's current object.

Unlike standard variables, which are determined by where we write the code (Lexical/ Static Scope), this has Dynamic Scoping. It is determined entirely by how the function is called.


Following rules are applied when function is defined with function declaration:

The 4 Rules of Invocation

1. New Binding

If the function is called with the new keyword, the engine creates a brand new object and binds this to it.

function User(name) {
  // 'this' = the new empty object created by the engine
  this.name = name;
}
 
const alice = new User("Alice");
console.log(alice.name); // "Alice"

2. Implicit Binding

If the function is called as a method of an object (it has a dot . before it), this points to the object on the left side of the dot.

const user = {
  name: "Charlie",
  sayHi: function () {
    console.log(this.name);
  },
};
 
// 'this' points to 'user' because it is to the left of the dot
user.sayHi(); // Output: "Charlie"

3. Default Binding

If none of the other rules apply (a standalone function call), the engine defaults to the global scope.

function showGlobal() {
  console.log(this);
}
 
showGlobal();
// Strict Mode: undefined
// Non-Strict Mode: Window / Global Object

4. Explicit Binding

This rule applies when we want to force a function to use a specific object as its context, overriding the Implicit or Default rules. We use three built-in methods which is inherited by all function objects.

A. .call()

  • Mechanism: Invokes the function immediately.
  • Arguments: Passed individually (comma-separated).
function greet(greeting) {
  console.log(`${greeting}, ${this.name}`);
}
const user = { name: "Bob" };
 
// Force 'greet' to use 'user' as 'this'
greet.call(user, "Hello"); // Output: "Hello, Bob"

B. .apply()

  • Mechanism: Invokes the function immediately.
  • Arguments: Passed as a single Array. Useful when we have a list of data.
const args = ["Hi", "!"];
greet.apply(user, args); // Output: "Hi, Bob!"

C. .bind()

  • Mechanism: Does NOT invoke the function. Instead, it returns a new function with this permanently locked to the specified context.
  • Use Case: Partial Application: Pre-setting common arguments to create specific utility functions
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}
 
const user = { name: "Bob" };
 
// Lock 'this' to user AND lock the first argument to "Hello"
const sayHelloToBob = greet.bind(user, "Hello");
 
// Later, we only need to provide the remaining argument(s)
sayHelloToBob("!"); // Output: "Hello, Bob!"
sayHelloToBob("?"); // Output: "Hello, Bob?"

The Arrow Function Exception

Arrow Functions (=>) do not follow the 4 Rules. They ignore call, new, and dots.

The Lookup Logic:

  • If inside a function: It adopts the parent function's this.
  • If top-level (or inside an object literal): It adopts the Global Object (Window).
const user = {
  name: "Alice",
  // Standard Function: "this" depends on caller
  sayHi: function () {
    console.log(this.name);
  },
 
  // Arrow Function: "this" looks OUTSIDE the object to Global Scope
  sayBye: () => {
    console.log(this.name);
  },
 
  // Nested Arrow: Inherits from 'sayHiDelayed' (the parent function)
  sayHiDelayed: function () {
    setTimeout(() => {
      // Logic: Arrow function is inside 'sayHiDelayed', so it grabs 'user'
      console.log(this.name);
    }, 1000);
  },
};
 
user.sayHi(); // "Alice" (Implicit Binding)
user.sayBye(); // undefined (Lexical Binding to Global)
user.sayHiDelayed(); // "Alice" (Works! Inherited from parent function)

📝 Summary Table

Explicit MethodBehaviorArguments
.call()Runs ImmediatelyIndividual (1, 2)
.apply()Runs ImmediatelyArray ([1, 2])
.bind()Returns FunctionIndividual (1, 2)