The this Keyword
this is one of the most-asked-about features of JavaScript, and most of the confusion goes away when you stop treating it as a variable and start treating it as a call-site mechanic. For regular functions, this is set at the moment the function is called, not where it is written. Arrow functions are the exception — they capture this from their surrounding scope.
Rule 1: regular function call
When you call a function as a plain function — fn() — this is undefined in strict mode (which all modules and classes use), or the global object in sloppy mode.
"use strict";
function whoAmI() {
return this;
}
console.log(whoAmI()); // undefinedRule 2: method call
When you call a function as a property of an object — obj.fn() — this is that object. The function does not have to be defined inside the object; what matters is the dot at the call site.
const user = {
name: "Ada",
greet() {
return "Hello, " + this.name;
},
};
console.log(user.greet()); // Hello, Ada
// Pull the method off — no more dot
const greet = user.greet;
// console.log(greet()); // TypeError: cannot read 'name' of undefinedRule 3: constructor call with new
Calling a function with new creates a fresh object and sets this to it for the duration of the call. The new object is returned automatically (unless the constructor explicitly returns another object).
function Point(x, y) {
this.x = x;
this.y = y;
}
const p = new Point(3, 4);
console.log(p); // Point { x: 3, y: 4 }Rule 4: arrow functions — lexical this
Arrow functions do not have their own this. They use the this of the enclosing scope at the time the arrow was defined. That is exactly what you usually want for inner callbacks.
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(() => {
this.seconds++; // `this` is the Timer — captured lexically
}, 1000);
}
}
// Compare with a regular function:
setInterval(function () {
// this === undefined here (strict mode)
}, 1000);Because arrows cannot have their own this, call, apply and bind are powerless to change it. They accept the argument and silently ignore it.
Rule 5: event handlers
When the DOM invokes a regular function as an event handler, this is the element the listener is attached to. With an arrow function, this is whatever it was outside.
button.addEventListener("click", function () {
// `this` is the button element
this.classList.toggle("active");
});
button.addEventListener("click", () => {
// `this` is whatever surrounds this code — usually not the button
// use `event.currentTarget` instead if you need the element
});call, apply and bind — explicit this
Every regular function has three methods that let you choose this manually.
fn.call(thisArg, a, b, c)— call the function now withthisArgand the listed arguments.fn.apply(thisArg, [a, b, c])— same, but arguments are an array.fn.bind(thisArg, a, b)— return a new function that, when called, will usethisArgand the pre-bound arguments.
function greet(greeting, punct) {
return `${greeting}, ${this.name}${punct}`;
}
const user = { name: "Ada" };
console.log(greet.call(user, "Hi", "!")); // Hi, Ada!
console.log(greet.apply(user, ["Hello", "."])); // Hello, Ada.
const sayHi = greet.bind(user, "Hey");
console.log(sayHi("?")); // Hey, Ada?The decision tree
To work out what `this` is at any call site, ask in order:
Is the function an arrow? Then
thisis whatever the surrounding scope says it is. Stop.Was it called with
new? Thenthisis the freshly-created object. Stop.Was it called with
call/apply/bind? Thenthisis whatever was passed. Stop.Was it called as
obj.fn()with a dot? Thenthisisobj. Stop.Otherwise (a plain
fn()),thisisundefinedin strict mode, or the global object in sloppy mode.