Posts
JavaScript 'this' Explained
JavaScript’s this is not difficult because it does many things. It is difficult because one small word changes value based on how a function is called. That makes this look unpredictable when the real problem is …
In this article
JavaScript’s this is not difficult because it does many things. It is difficult because one small word changes value based on how a function is called.
That makes this look unpredictable when the real problem is usually that we are looking in the wrong place.
For an ordinary function, do not start with where the function was written. Start with the call-site.
The call usually decides
this.
That one rule explains most of the behavior you will meet in day-to-day JavaScript.
Scope Is Not this
Scope answers this question:
Where can JavaScript find this variable?
this answers a different question:
What value is this function being called with as its receiver?
Consider a regular method:
const user = {
name: 'Ada',
sayName() {
console.log(this.name);
}
};
user.sayName();
// Ada
JavaScript does not search outward through lexical scopes to find this. The call user.sayName() supplies user as the receiver.
user.sayName()
^^^^
this points here
That is the first useful mental model: look to the left of the dot at the call-site.
Method Calls Provide a Receiver
The property that stores a function does not permanently attach that function to the object. The object matters when the function is called through it.
function introduce() {
console.log(`Hi, I am ${this.name}.`);
}
const ada = {
name: 'Ada',
introduce
};
const grace = {
name: 'Grace',
introduce
};
ada.introduce();
// Hi, I am Ada.
grace.introduce();
// Hi, I am Grace.
It is the same function in both objects. The receiver changes because the call-site changes.
Detached Methods Lose Their Receiver
Now take the method out of the object:
const user = {
name: 'Ada',
sayName() {
console.log(this.name);
}
};
const sayName = user.sayName;
sayName();
The final call is no longer user.sayName(). It is a plain sayName() call, so user is not supplied as the receiver.
In strict mode, this is undefined for that plain function call, and reading this.name throws a TypeError. In a classic non-strict browser script, this may fall back to globalThis. Modules and class bodies are strict, so relying on the fallback is a fine way to manufacture a bug that changes when code moves.
The function did not forget user. It was never permanently bound to user in the first place.
Set this Explicitly with call, apply, or bind
JavaScript also lets you choose the receiver.
Use call for Individual Arguments
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'Ada' };
console.log(greet.call(user, 'Hello', '!'));
// Hello, Ada!
The first argument to call becomes this. The remaining values become function arguments.
Use apply for an Array of Arguments
console.log(greet.apply(user, ['Welcome', '.']));
// Welcome, Ada.
apply does the same receiver selection but accepts the function arguments in an array-like value.
Use bind to Create a Bound Function
const greetAda = greet.bind(user);
console.log(greetAda('Good morning', '!'));
// Good morning, Ada!
bind does not call greet immediately. It creates a new function whose this value is fixed to user for normal calls.
This is useful when you must pass a method as a callback but still need its receiver:
const user = {
name: 'Ada',
sayName() {
console.log(this.name);
}
};
setTimeout(user.sayName.bind(user), 100);
// Ada
Constructor Calls Create this
Calling a constructable function with new follows another rule: JavaScript creates a new object and supplies it as this.
function User(name) {
this.name = name;
}
const ada = new User('Ada');
console.log(ada.name);
// Ada
At a practical level, new User('Ada') does this work:
- Creates a new object linked to
User.prototype. - Calls
Userwith that object asthis. - Returns the new object unless the constructor explicitly returns a different object.
Classes package this behavior in clearer syntax, but the receiver rule is still doing the work underneath.
Nested Regular Functions Get Their Own this
A regular function inside a method does not automatically inherit the method’s receiver.
const counter = {
count: 0,
start() {
setTimeout(function () {
console.log(this === counter);
// false
}, 100);
}
};
counter.start();
The callback is invoked later by the timer machinery. It is not called as counter.callback(), so it does not receive counter as this.
You can solve that with bind, an ordinary variable captured through closure, or an arrow function. Arrow functions use a different this rule, so I cover that separately in JavaScript Arrow Functions Explained.
The Practical Lookup Order
When an ordinary function uses this, inspect the call in this order:
- Was it called with
new? The newly created object becomesthis. - Was it called with
call,apply, or a function created bybind? The explicitly supplied value is used. - Was it called through an object, as in
object.method()? That object is the receiver. - Was it called as a plain function? Strict mode uses
undefined; non-strict code may substituteglobalThis.
There are specification details and edge cases behind those rules, but this checklist handles the bugs most developers actually need to diagnose.
Quick Recap
thisis not lexical scope.- An ordinary function usually gets
thisfrom its call-site. object.method()suppliesobjectas the receiver.- Detaching a method also detaches that receiver.
call,apply, andbindlet you choosethisexplicitly.newsupplies a newly created object asthis.- Arrow functions do not follow these rules because they do not define their own
this.
When this looks mysterious, find the call. The mystery usually gets much smaller from there.
Related JavaScript Posts
- JavaScript Arrow Functions Explained explains why arrows capture
thisinstead of receiving it from the call-site. - JavaScript Closures and Modified Closures Explained covers the lexical environment an arrow can close over.
- JavaScript Function Parameters and Arguments Explained separates the values supplied by a call from the parameters that receive them.
-Rob