Understanding `this`
The this keyword is one of JavaScript's most misunderstood features. Learn the 5 rules that determine what this refers to โ and how arrow functions change everything.
What is this?
this is a special keyword that refers to an object โ but which object depends entirely on how the function is called, not where it's defined. This is the key insight.
There are 5 rules (in order of precedence) that determine what this is:
newbinding- Explicit binding (
call,apply,bind) - Implicit binding (method call)
- Default binding (standalone call)
- Arrow function (lexical binding โ no own
this)
Rule 1: Default Binding
When a function is called as a plain function (no object context), this defaults to globalThis (window in browsers) in non-strict mode, or undefined in strict mode:
function showThis() {
console.log(this);
}
showThis(); // window (browser) or global (Node) or undefined (strict mode)
// In strict mode ("use strict"):
function showStrict() {
"use strict";
console.log(this); // undefined
}
showStrict();
โ Tip
Modern JavaScript (ES modules) and class bodies are always in strict mode. So a standalone function call gives this === undefined.
Rule 2: Implicit Binding
When a function is called as a method of an object, this is the object before the dot:
const user = {
name: "Alice",
greet() {
return `Hello, I'm ${this.name}`;
},
};
console.log(user.greet()); // "Hello, I'm Alice"
// this === user
The pitfall โ losing this context:
const greet = user.greet; // extract the function
console.log(greet()); // "Hello, I'm undefined"
// this is no longer user โ it's the global object!
// Common trap with callbacks:
const buttons = {
label: "Click me",
setup() {
// 'this' here is buttons
setTimeout(function() {
// 'this' here is window! (callback loses context)
console.log(this.label); // undefined
}, 100);
},
};
Rule 3: Explicit Binding
Use call, apply, or bind to explicitly set this:
function introduce(greeting, punctuation) {
return `${greeting}, I'm ${this.name}${punctuation}`;
}
const person = { name: "Bob" };
// call โ arguments passed individually
introduce.call(person, "Hello", "!"); // "Hello, I'm Bob!"
// apply โ arguments passed as array
introduce.apply(person, ["Hey", "."]); // "Hey, I'm Bob."
// bind โ creates a NEW function with this permanently bound
const bobIntro = introduce.bind(person);
bobIntro("Hi", "?"); // "Hi, I'm Bob?"
// bind can also pre-fill arguments (partial application)
const bobHello = introduce.bind(person, "Hello");
bobHello("!"); // "Hello, I'm Bob!"
bobHello("?"); // "Hello, I'm Bob?"
Rule 4: new Binding
When a function is called with new, a new object is created and this refers to that new object:
function Car(make, model) {
this.make = make; // this = new object being created
this.model = model;
this.toString = function() {
return `${this.make} ${this.model}`;
};
}
const toyota = new Car("Toyota", "Camry");
const honda = new Car("Honda", "Civic");
console.log(toyota.make); // "Toyota"
console.log(String(toyota)); // "Toyota Camry"
console.log(String(honda)); // "Honda Civic"
new binding overrides all other rules.
Rule 5: Arrow Functions (Lexical this)
Arrow functions do not have their own this. They inherit this from the enclosing lexical scope at the time they're defined:
const timer = {
seconds: 0,
// โ Regular function โ loses this
startBroken() {
setInterval(function() {
this.seconds++; // this is window, not timer!
console.log(this.seconds);
}, 1000);
},
// โ Arrow function โ lexically inherits this
start() {
setInterval(() => {
this.seconds++; // this is timer โ
console.log(this.seconds);
}, 1000);
},
};
Arrow functions are the modern fix for this context loss in callbacks.
The 5-Rule Precedence
When determining this, check rules in this order:
// 1. new binding
const obj1 = new Foo(); // this = new empty object
// 2. Explicit: call/apply/bind
foo.call(obj2); // this = obj2
const bound = foo.bind(obj3); // this = obj3
// 3. Implicit: method call
obj4.foo(); // this = obj4
// 4. Default: standalone
foo(); // this = undefined (strict) or global
// 5. Arrow: lexical (none of the above apply)
const arrowFn = () => this; // this = whatever outer scope's this is
Common Patterns
Saving this as a variable (old pattern)
const self = this; // or: const that = this;
setTimeout(function() {
console.log(self.name); // use saved reference
}, 100);
Arrow function fix (modern pattern)
setTimeout(() => {
console.log(this.name); // arrow inherits this
}, 100);
Explicit bind in constructors
class Button {
constructor() {
this.count = 0;
// Bind in constructor so the method always has correct this
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.count++;
console.log("Clicked", this.count, "times");
}
}
Class field arrow function (modern React pattern)
class Button {
count = 0;
// Class field with arrow function โ automatically bound
handleClick = () => {
this.count++;
console.log("Clicked", this.count, "times");
};
}
this in Classes
In class methods, this refers to the instance when called properly:
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, I'm ${this.name}`;
}
delayedGreet() {
// Arrow captures this from the method's context
setTimeout(() => {
console.log(this.greet());
}, 100);
}
}
const alice = new Person("Alice");
alice.greet(); // "Hi, I'm Alice"
alice.delayedGreet(); // Works! "Hi, I'm Alice"
// Detaching the method loses this:
const fn = alice.greet;
// fn(); // Error: Cannot read property 'name' of undefined (strict mode)
Key Takeaways
thisis determined by how a function is called, not where it's defined- 5 rules:
new> explicit (call/apply/bind) > method call > standalone > arrow (lexical) - Arrow functions don't have their own
thisโ they inherit from the enclosing scope - Detaching a method from its object breaks implicit binding
- Use arrow functions for callbacks to preserve
thiscontext - Use
.bind()to permanently attach athisvalue
Ready to test your knowledge?
Take a quiz on what you just learned.