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:
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"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"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 ObjectThis 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()
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()
const args = ["Hi", "!"];
greet.apply(user, args); // Output: "Hi, Bob!"C. .bind()
this permanently locked to the specified context.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?"Arrow Functions (=>) do not follow the 4 Rules. They ignore call, new, and dots.
The Lookup Logic:
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)| Explicit Method | Behavior | Arguments |
|---|---|---|
.call() | Runs Immediately | Individual (1, 2) |
.apply() | Runs Immediately | Array ([1, 2]) |
.bind() | Returns Function | Individual (1, 2) |