The 'this' Keyword Explained
In JavaScript, this is a keyword whose value is determined dynamically at call time (how and where a function is invoked), rather than where the function is defined. The only exception is arrow functions, which use lexical binding.
The 5 Rules of this Binding
To determine what this refers to in any function call, evaluate invocation against these 5 precedence rules:
Binding Precedence Hierarchy (Highest to Lowest)
┌─────────────────────────────────────────┐
│ 1. 'new' Binding │
│ 2. Explicit Binding (call, apply, bind) │
│ 3. Implicit Binding (Context Object) │
│ 4. Default / Global Binding │
│ 5. Lexical Binding (Arrow Functions) │
└─────────────────────────────────────────┘
1. Implicit Binding (Object Context)
When a function is invoked as a method of an object (using dot notation), this points to the object preceding the dot:
const user = {
name: "Alex",
greet() {
return `Hello, my name is ${this.name}`;
}
};
console.log(user.greet()); // "Hello, my name is Alex"
Assigning a method to a separate variable strips its object context:
const unboundGreet = user.greet;
console.log(unboundGreet()); // "Hello, my name is undefined" (or throws in strict mode)
2. Explicit Binding (call, apply, bind)
You can explicitly force a function to execute with a specific this context using JavaScript's built-in prototype methods:
| Method | Invocation | Arguments Format | Execution |
|---|---|---|---|
call() | Direct | Comma-separated (obj, arg1, arg2) | Executes immediately |
apply() | Direct | Array of arguments (obj, [arg1, arg2]) | Executes immediately |
bind() | Indirect | Comma-separated (obj, arg1, arg2) | Returns a new bound function |
function updateProfile(role, location) {
this.role = role;
this.location = location;
return `${this.name} is a ${this.role} in ${this.location}`;
}
const person = { name: "Jordan" };
// 1. call
console.log(updateProfile.call(person, "Lead Architect", "Berlin"));
// 2. apply
console.log(updateProfile.apply(person, ["Principal Engineer", "Remote"]));
// 3. bind
const boundFn = updateProfile.bind(person, "DevOps Manager", "Tokyo");
console.log(boundFn());
3. new Binding (Constructor Invocation)
When a function is called with the new keyword:
- A brand new empty object
{}is created. - The object is linked to the function's prototype (
[[Prototype]]). thisinside the function is bound to that newly created object.- The function implicitly returns the object (unless a different object is returned explicitly).
function Developer(name, language) {
this.name = name;
this.language = language;
}
const dev = new Developer("Sam", "JavaScript");
console.log(dev.name); // "Sam"
4. Default / Global Binding
When a standalone function is called without any context object:
- In Non-Strict Mode:
thisdefaults to the global object (windowin browsers,globalin Node.js). - In Strict Mode (
"use strict"):thisevaluates toundefined.
function checkContext() {
"use strict";
return this;
}
console.log(checkContext()); // undefined
5. Lexical this (Arrow Functions)
Arrow functions do not have their own this. They capture the this value from their enclosing execution scope at the time they are created:
const company = {
name: "CodeHarborHub",
getBrand: function () {
return `Company: ${this.name}`;
},
getBrandArrow: () => {
return `Company: ${this.name}`; // 'this' is lexically bound to the outer scope (global)
}
};
const timer = {
seconds: 0,
start() {
// Arrow function lexically captures 'this' from start() method context
setInterval(() => {
this.seconds++;
console.log(`Elapsed: ${this.seconds}s`);
}, 1000);
}
};
Interactive Playground: this Binding Lab
Test explicit binding, loss of implicit context, and arrow function behaviors live:
Best Practices
- Use Arrow Functions for Callbacks: Preserve outer
thisinside event listeners, timers, and array methods without needingconst self = thisor explicit.bind(this). - Avoid Arrow Functions for Object Methods: Defining object methods with arrow functions will bind
thisto the outer global scope instead of the object itself. - Always Enable Strict Mode: Prevent accidental global variable mutations caused by default
thisbinding in standalone functions.
Knowledge Check
Exercise Requirements:
Fix the bugs in the object below so getDetails correctly logs the user's details without throwing TypeError or logging undefined.
const userProfile = {
username: "CodeNinja",
skills: ["JS", "TS", "React"],
getDetails: () => {
return `${this.username} knows ${this.skills.join(", ")}`;
}
};
const userProfile = {
username: "CodeNinja",
skills: ["JS", "TS", "React"],
// Replace arrow function with standard method definition
getDetails() {
return `${this.username} knows ${this.skills.join(", ")}`;
}
};
console.log(userProfile.getDetails());
// Output: "CodeNinja knows JS, TS, React"
Congratulations! You have completed Phase 03: Deep Dive Core. Proceed to Phase 04: Asynchronous JavaScript to master the event loop, promises, and async/await!