this is a special variable that refers to the object a function is working on. Unlike ordinary variables, it is not fixed where the function is written. For regular functions it is decided each time the function is called. Learn the four rules below and this stops being mysterious.
Rule 1: called as a method, this is the object before the dot
const course = {
title: 'JavaScript Complete Course',
describe() {
return `This is the ${this.title}`;
},
};
console.log(course.describe());
// → This is the JavaScript Complete Course
course.describe() has course before the dot, so inside describe, this is course. The same function can serve many objects:
function describe() {
return `Title: ${this.title}`;
}
const a = { title: 'HTML', describe };
const b = { title: 'CSS', describe };
console.log(a.describe()); // → Title: HTML
console.log(b.describe()); // → Title: CSS
Rule 2: called on its own, this is undefined
Call a regular function without an object in front of it and this is undefined in strict mode. ES modules and classes are always strict, and you should assume strict mode everywhere.
'use strict';
function whoAmI() {
return this;
}
console.log(whoAmI()); // → undefined
In old non-strict scripts, this falls back to the global object (window in browsers). That hides bugs, which is one reason strict mode exists.
The lost this
Rule 2 causes the most common this bug. Take a method off its object, and the object is no longer before the dot when it is called:
'use strict';
const timer = {
seconds: 0,
tick() {
this.seconds += 1;
console.log(this.seconds);
},
};
timer.tick(); // → 1
const tick = timer.tick;
tick();
// TypeError: Cannot read properties of undefined (reading 'seconds')
You rarely write const tick = timer.tick deliberately. It happens implicitly whenever you pass a method as a callback:
setTimeout(timer.tick, 1000);
// The timer calls tick() on its own, so this is not timer.
setTimeout receives the function only. It has no idea that the function came from timer. (In browsers, timers call the callback with this set to window, so you get NaN instead of an error, which is even harder to spot.)
There are three standard fixes.
Fix 1: wrap it in an arrow function
setTimeout(() => timer.tick(), 1000);
The arrow function calls timer.tick() with timer before the dot, so rule 1 applies. This is the most readable fix and the one to reach for first.
Fix 2: bind it
bind returns a new function with this permanently set:
const boundTick = timer.tick.bind(timer);
setTimeout(boundTick, 1000);
Binding is useful when you need to keep a reference to the same function, for example to remove an event listener later.
Fix 3: use an arrow function inside the method
See rule 4 below.
Rule 3: call and apply set this explicitly
call and apply invoke a function immediately with the this you choose. They differ only in how they take arguments:
function introduce(greeting, punctuation) {
return `${greeting}, I am ${this.name}${punctuation}`;
}
const person = { name: 'Priya' };
console.log(introduce.call(person, 'Hello', '!'));
// → Hello, I am Priya!
console.log(introduce.apply(person, ['Hi', '.']));
// → Hi, I am Priya.
| Method | Runs the function? | Arguments |
|---|---|---|
fn.call(obj, a, b) | Yes, now | Listed one by one |
fn.apply(obj, [a, b]) | Yes, now | One array |
fn.bind(obj, a) | No, returns a new function | Optional, fixed in front |
With spread syntax (lesson 23) you can write fn.call(obj, ...args), so apply is rarely needed in new code.
Calling a function with new is a fourth way to set this: it becomes the newly created object. Classes in lesson 36 build on this.
Rule 4: arrow functions do not have their own this
An arrow function ignores all the rules above. It uses the this of the scope it was written in, exactly like an ordinary variable from an outer scope. That makes arrows ideal for callbacks inside methods:
const quiz = {
title: 'Capitals',
questions: ['France', 'Japan'],
list() {
this.questions.forEach((country) => {
console.log(`${this.title}: ${country}`);
});
},
};
quiz.list();
// → Capitals: France
// → Capitals: Japan
The arrow callback borrows this from list, which was called as quiz.list(). With a regular function callback, this inside it would be undefined and the code would throw.
The same feature makes arrows the wrong choice for methods:
const broken = {
title: 'Capitals',
describe: () => `Quiz: ${this.title}`,
};
console.log(broken.describe());
// In a module: TypeError, because this is undefined at the top level
The arrow was written at the top level of the file, so it takes the top level this, not the object. Use method shorthand (describe() { }) for methods and arrows for callbacks inside them. bind, call and apply cannot change an arrow function's this either.
this in event listeners
When the browser calls a regular function as an event listener, this is the element the listener is attached to, the same as event.currentTarget:
const button = document.querySelector('#save');
button.addEventListener('click', function () {
console.log(this === button); // → true
});
With an arrow function, this would come from the surrounding code instead. Using event.currentTarget makes the intent clear whichever kind of function you write, so prefer it:
button.addEventListener('click', (event) => {
event.currentTarget.disabled = true;
});
A quick decision list
To find this inside a function, ask these questions in order:
- Is it an arrow function? Then
thisis whatever it is in the surrounding code. - Was it called with
new? Thenthisis the new object. - Was it called with
callorapply, or created withbind? Thenthisis the object passed in. - Was it called as
something.method()? Thenthisissomething. - Otherwise,
thisisundefinedin strict mode.
Summary
- For regular functions,
thisis decided by how the function is called, not where it is written. obj.method()setsthistoobj; a plain call leaves itundefinedin strict mode.- Passing a method as a callback loses
this. Fix it with an arrow wrapper orbind. callandapplyrun a function with a chosenthis;bindreturns a new bound function.- Arrow functions take
thisfrom their surroundings: use them for callbacks, not for methods.