The 'this' Keyword in JavaScript Objects Guide
Understand how the this keyword works in JavaScript objects. Covers implicit binding, explicit binding with call/apply/bind, arrow function behavior, common pitfalls, and practical patterns for preserving this context.
The this keyword is one of JavaScript's most confusing features for beginners. Unlike other languages where this always points to the class instance, JavaScript's this changes depending on how a function is called, not where it is defined. This dynamic behavior makes this both powerful and error-prone, especially when working with object methods, callbacks, and event handlers.
This guide covers every binding rule, shows you how this behaves in different contexts, and teaches you reliable patterns for controlling what this points to.
What is this?
this is a special keyword that refers to the current execution context. In the context of an object method, this typically refers to the object that owns the method:
const user = {
name: "Alice",
greet() {
console.log(this); // The user object itself
console.log(this.name); // "Alice"
}
};
user.greet(); // `this` = userThe Four Binding Rules
JavaScript determines the value of this using four rules, checked in this priority order:
| Priority | Rule | Example | this Value |
|---|---|---|---|
| 1 (Highest) | new binding | new User() | New instance |
| 2 | Explicit binding | fn.call(obj) | obj |
| 3 | Implicit binding | obj.method() | obj |
| 4 (Lowest) | Default binding | fn() | undefined (strict) / window (sloppy) |
Rule 1: Implicit Binding (Method Call)
When you call a function as a method of an object, this refers to the object on the left side of the dot:
const calculator = {
value: 0,
add(n) {
this.value += n;
return this;
},
subtract(n) {
this.value -= n;
return this;
},
getResult() {
return this.value;
}
};
calculator.add(10).subtract(3);
console.log(calculator.getResult()); // 7
// `this` refers to `calculator` in each method callRule 2: Explicit Binding (call, apply, bind)
You can explicitly set this using call(), apply(), or bind():
function introduce() {
return `I'm ${this.name}, age ${this.age}`;
}
const alice = { name: "Alice", age: 28 };
const bob = { name: "Bob", age: 32 };
// call: invoke immediately with `this` set
console.log(introduce.call(alice)); // "I'm Alice, age 28"
console.log(introduce.call(bob)); // "I'm Bob, age 32"call vs apply vs bind
function formatUser(greeting, punctuation) {
return `${greeting}, I'm ${this.name}${punctuation}`;
}
const user = { name: "Alice" };
// call: arguments passed individually
console.log(formatUser.call(user, "Hello", "!"));
// "Hello, I'm Alice!"
// apply: arguments passed as array
console.log(formatUser.apply(user, ["Hey", "."]));
// "Hey, I'm Alice."
// bind: returns a NEW function with `this` permanently set
const greetAlice = formatUser.bind(user, "Hi");
console.log(greetAlice("?")); // "Hi, I'm Alice?"
console.log(greetAlice("!")); // "Hi, I'm Alice!"| Method | Invokes Immediately? | Arguments Format | Returns |
|---|---|---|---|
call(thisArg, a, b) | Yes | Individual | Return value |
apply(thisArg, [a, b]) | Yes | Array | Return value |
bind(thisArg, a) | No | Individual (partial) | New bound function |
Rule 3: new Binding
When a function is called with new, this refers to the newly created instance:
function Product(name, price) {
// `this` is the new empty object
this.name = name;
this.price = price;
this.inStock = true;
}
const laptop = new Product("Laptop", 999);
console.log(laptop.name); // "Laptop"
console.log(laptop.inStock); // trueRule 4: Default Binding
When a function is called without any object context, this falls back to the default:
"use strict";
function showThis() {
console.log(this);
}
showThis(); // undefined (strict mode)
// In non-strict mode, this would be the global object (window in browser)Arrow Functions and this
Arrow functions do not have their own this. They inherit this from the enclosing lexical scope at the time they are defined:
const team = {
name: "Engineering",
members: ["Alice", "Bob", "Charlie"],
// Regular method: `this` = team
showMembers() {
// Arrow function: inherits `this` from showMembers
this.members.forEach(member => {
console.log(`${member} is on the ${this.name} team`);
});
}
};
team.showMembers();
// "Alice is on the Engineering team"
// "Bob is on the Engineering team"
// "Charlie is on the Engineering team"Why Arrow Functions Fail as Methods
const user = {
name: "Alice",
// WRONG: arrow function as method
greet: () => {
return `Hi, I'm ${this.name}`;
// `this` is inherited from the outer scope, NOT from `user`
},
// CORRECT: regular method
greetCorrect() {
return `Hi, I'm ${this.name}`;
}
};
console.log(user.greet()); // "Hi, I'm undefined"
console.log(user.greetCorrect()); // "Hi, I'm Alice"Common this Pitfalls
Losing this in Callbacks
The most frequent this bug happens when passing a method as a callback:
const counter = {
count: 0,
increment() {
this.count++;
console.log(`Count: ${this.count}`);
}
};
// PROBLEM: `this` is lost
setTimeout(counter.increment, 100);
// "Count: NaN" (this.count is undefined.count + 1)
// SOLUTION 1: bind
setTimeout(counter.increment.bind(counter), 100);
// "Count: 1"
// SOLUTION 2: arrow wrapper
setTimeout(() => counter.increment(), 100);
// "Count: 2"
// SOLUTION 3: direct call within callback
setTimeout(function() { counter.increment(); }, 100);
// "Count: 3"Losing this in Array Methods
const store = {
discount: 0.1,
prices: [100, 200, 300],
// WRONG: regular function in map loses `this`
getDiscountedWrong() {
return this.prices.map(function(price) {
return price * (1 - this.discount); // `this` is undefined!
});
},
// CORRECT: arrow function preserves `this`
getDiscounted() {
return this.prices.map(price => {
return price * (1 - this.discount); // `this` = store
});
}
};
console.log(store.getDiscounted()); // [90, 180, 270]this in Nested Objects
const company = {
name: "TechCorp",
department: {
name: "Engineering",
getName() {
return this.name; // `this` = department, not company
}
}
};
console.log(company.department.getName()); // "Engineering" (not "TechCorp")Patterns for Preserving this
Pattern 1: Self / That Variable (Legacy)
const timer = {
seconds: 0,
start() {
const self = this; // Capture `this` reference
setInterval(function() {
self.seconds++;
console.log(self.seconds);
}, 1000);
}
};Pattern 2: Arrow Functions (Modern)
const timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds++; // Arrow inherits `this` from start()
console.log(this.seconds);
}, 1000);
}
};Pattern 3: bind() (Explicit)
const timer = {
seconds: 0,
tick() {
this.seconds++;
console.log(this.seconds);
},
start() {
setInterval(this.tick.bind(this), 1000);
}
};Comparison of Approaches
| Pattern | Readability | Modern | Works in All Contexts |
|---|---|---|---|
| Arrow function | Clean | Yes | Yes (when used in callbacks inside methods) |
.bind(this) | Explicit | Yes | Yes |
const self = this | Verbose | Legacy | Yes |
Practical Example: Event-Driven Shopping Cart
class ShoppingCart {
constructor() {
this.items = [];
this.taxRate = 0.08;
}
addItem(name, price, qty = 1) {
this.items.push({ name, price, qty });
return this;
}
removeItem(name) {
this.items = this.items.filter(item => item.name !== name);
return this;
}
getSubtotal() {
return this.items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
getTax() {
return this.getSubtotal() * this.taxRate;
}
getTotal() {
return this.getSubtotal() + this.getTax();
}
summary() {
const lines = this.items.map(item =>
` ${item.name} x${item.qty}: $${(item.price * item.qty).toFixed(2)}`
);
lines.push(` Subtotal: $${this.getSubtotal().toFixed(2)}`);
lines.push(` Tax (${(this.taxRate * 100)}%): $${this.getTax().toFixed(2)}`);
lines.push(` Total: $${this.getTotal().toFixed(2)}`);
return lines.join("\n");
}
}
const cart = new ShoppingCart()
.addItem("Laptop", 999.99)
.addItem("Mouse", 29.99, 2)
.addItem("Keyboard", 79.99);
console.log(cart.summary());
// Shows itemized list with subtotal, tax, and total
// Every method uses `this` to access cart data correctlyBest Practices
- Use regular functions/methods for object methods (not arrow functions)
- Use arrow functions inside methods for callbacks that need the parent
this - Use bind() when you need to pass a method reference that preserves
this - Avoid
self = thisin new code; use arrow functions instead - Use strict mode to catch accidental global
thisreferences (they becomeundefinedinstead ofwindow)
Rune AI
Key Insights
- Implicit binding:
obj.method()setsthistoobjautomatically - Explicit binding:
call,apply, andbindlet you manually control whatthispoints to - Arrow functions: Inherit
thisfrom their enclosing scope, making them ideal for callbacks inside methods - Common pitfall: Extracting a method or passing it as a callback loses
thisunless you usebindor an arrow wrapper - Strict mode: Makes default
thisbeundefinedinstead of the global object, helping catch bugs early
Frequently Asked Questions
Why does JavaScript not just use the object as `this` automatically?
Is `this` the same as `self` in Python?
Can I completely avoid using `this` in JavaScript?
What does `this` refer to in the global scope?
How does `this` work with class fields?
Conclusion
The this keyword in JavaScript is context-dependent. Implicit binding works when calling methods directly on objects, explicit binding (call, apply, bind) forces a specific context, new binding creates instances, and arrow functions lock in the surrounding scope's this. Most this bugs come from passing methods as callbacks without preserving their context. Once you understand the four binding rules and adopt arrow functions for nested callbacks, this becomes predictable and manageable.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.