Skip to content
Tien Nguyen logo Tien Nguyen
Back to blog

The this keyword in JavaScript, actually explained

Tien Nguyen

Tien Nguyen

· 9 min read

The this keyword in JavaScript, actually explained
Frontend craftJavascript

Most developers think the this keyword in JavaScript is confusing. It isn’t. It follows one rule that most explanations bury under a list of special cases:

this is set by how a function is called, not where it’s written.

That’s the whole thing. Every weird this behavior you’ve hit, every “why is this undefined” at 2am, comes from reading the wrong place. You look at where the function is defined. JavaScript looks at where it’s called. Get that one idea and the four “binding rules” everyone lists become obvious instead of a menu you memorize.

Let me show you the rule, the four cases it produces, and the one place it actually breaks in real code (spoiler: it’s a callback, and it’s probably in your event handlers).

Read the call-site, not the definition

When a function runs, JavaScript has to decide what this points to. It doesn’t look at the function body. It looks at the call-site: the exact expression that invoked the function. Read left of the dot and you almost always have your answer.

call-site.js
function whoAmI() {
return this;
}
const user = { name: 'Tien', whoAmI };
user.whoAmI(); // this is `user` (called as user.something)
whoAmI(); // this is undefined (called standalone, in strict mode)

Same function. Two call-sites. Two values of this. Nothing about the definition changed, so the definition can’t be what decides it.

Once you’re reading the call-site, there are exactly four shapes it can take.

The four ways the this keyword gets its value

These are usually called the binding rules. They’re just four kinds of call-site, ranked by precedence.

The four this binding rules in JavaScript, in precedence order: new, explicit call/apply/bind, implicit method call, default standalone call

1. Default binding: a standalone call. No object, no dot. this is undefined in strict mode (which every ES module and class body uses), or the global object (window) in old sloppy mode.

default.js
function greet() {
return `hi, ${this?.name}`;
}
greet(); // "hi, undefined", no call-site object, so no this

2. Implicit binding: a method call. The function is called as a property of an object. this is the object left of the dot. This is the one people intuit correctly.

implicit.js
const user = {
name: 'Tien',
greet() {
return `hi, ${this.name}`;
},
};
user.greet(); // "hi, Tien" (this is `user`)

3. Explicit binding: call, apply, or bind. You hand JavaScript the this yourself. call and apply invoke immediately (apply takes args as an array). bind returns a new function with this locked in permanently.

explicit.js
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
const user = { name: 'Tien' };
greet.call(user, 'hi'); // "hi, Tien"
greet.apply(user, ['yo']); // "yo, Tien"
const greetTien = greet.bind(user);
greetTien('hello'); // "hello, Tien" (locked to `user` forever)

4. new binding: a constructor call. new Fn() creates a fresh object and points this at it. That’s how constructors and (under the hood) class instances get their this.

new.js
function User(name) {
this.name = name; // this is the brand-new object
}
const u = new User('Tien');
u.name; // "Tien"

When more than one could apply, precedence runs top down: new beats explicit bind, explicit beats implicit, implicit beats default. That’s the whole ladder in the graphic above.

Does an arrow function have its own this?

No. Here’s the exception that feels like a fifth rule but isn’t. An arrow function has no this of its own. It ignores the call-site entirely and uses this from the scope where it was written. This is the one time “where it’s defined” wins, and it’s deliberate.

arrow-lexical.js
const user = {
name: 'Tien',
greetLater() {
setTimeout(() => {
console.log(`hi, ${this.name}`); // "hi, Tien"
}, 100);
},
};
user.greetLater();

The arrow inside setTimeout has no this, so it reaches out to greetLater’s this, which is user. Swap that arrow for a regular function () { ... } and it logs hi, undefined, because setTimeout calls it standalone. Hold onto that, because it’s the entire reason the next section matters.

Why does this break inside a callback?

Most explanations list the four rules and stop there. The one place they actually bite in production is this: the moment you pass a method somewhere else, you detach it from its object.

the-bug.js
const user = {
name: 'Tien',
greet() {
return `hi, ${this.name}`;
},
};
const fn = user.greet;
fn(); // TypeError: this is undefined, so this.name throws (strict mode)

Nothing is broken about greet. But const fn = user.greet copies the function, not the user. in front of it. When fn() runs, the call-site has no object, so you’re back to default binding: this is undefined (strict mode), and reading .name off undefined throws. In old sloppy mode it wouldn’t throw at all: this falls back to the global object, so you’d get a silently wrong answer instead of a crash. Either way, the user. was never part of the function. It was part of the call.

This is not a toy example. It’s exactly what happens every time you write:

detached.js
element.addEventListener('click', user.greet); // this = the clicked element
setTimeout(user.greet, 100); // this = the global object (window)
[1, 2, 3].forEach(user.greet); // this = undefined, so greet throws

You handed the method to something that will call it later, standalone. The object got left behind at the door.

The React handler version (the one that actually costs people hours)

If you’ve written React class components, you’ve hit this and maybe didn’t know why:

broken-handler.jsx
class Toggle extends React.Component {
state = { on: false };
handleClick() {
this.setState({ on: !this.state.on });
}
render() {
return <button onClick={this.handleClick}>toggle</button>;
}
}
// Click it: "Cannot read properties of undefined (reading 'setState')"

onClick={this.handleClick} passes the method as a reference. React calls it standalone when the click fires, so this is undefined, and this.setState blows up. Same bug as const fn = user.greet, wearing a JSX costume.

The fix is any of the three ways to pin this. The cleanest is a class field arrow, because an arrow captures this lexically at definition time:

fixed-handler.jsx
class Toggle extends React.Component {
state = { on: false };
handleClick = () => {
this.setState({ on: !this.state.on }); // arrow field: this = the instance
};
render() {
return <button onClick={this.handleClick}>toggle</button>;
}
}

(Function components and hooks sidestep the whole thing, which is a big part of why they won. There’s no this to lose.)

What is this inside a JavaScript class?

Inside a class method, this is the instance, as long as you call it as instance.method(). Classes run in strict mode, so the second you detach a method, this is undefined, not the global object. That strictness is a feature: it turns a silent wrong-object bug into a loud crash you can actually find.

class.js
class Counter {
count = 0;
increment() {
this.count++;
}
}
const c = new Counter();
c.increment(); // fine, this = c
const inc = c.increment;
inc(); // TypeError: Cannot read properties of undefined

A class field arrow (increment = () => { ... }) auto-binds to the instance, which is why it’s the common fix for handlers. The tradeoff: an arrow field lives on each instance instead of the shared prototype, so it costs a little memory per object. For event handlers that’s a fine trade. For a method you’ll create a million of, prefer a regular method plus one bind in the constructor.

Quick reference: read the call-site

When this surprises you, find the call-site and match it here:

How the function is calledWhat this is
fn() (standalone)undefined (strict) or global (sloppy)
obj.fn()obj (the thing left of the dot)
fn.call(x) / fn.apply(x) / fn.bind(x)()x
new Fn()the brand-new instance
an arrow functionthis of the enclosing scope (call-site ignored)
el.addEventListener('click', fn) with a regular fnthe element

How to debug a wrong-this bug

When this is not what you expect, run this checklist in order. It resolves nearly every case in under a minute:

  1. Find the call-site. Not where the function is defined, where it’s invoked. Is it obj.method() or a bare method()?
  2. Arrow or regular? An arrow ignores the call-site and uses the surrounding this. A regular function obeys the call-site. Mixing these up is the most common mistake.
  3. Was the method passed as a reference? onClick={this.x}, setTimeout(obj.m), arr.forEach(obj.m) all detach the method. That’s default binding, and in strict mode this is undefined.
  4. Strict mode? Modules, classes, and 'use strict' make standalone this undefined instead of window. If you see undefined where you expected the global object, that’s why.

The takeaway

this isn’t magic and it isn’t random. It’s late-bound: JavaScript waits until the function is called and then reads the call-site. Four call-site shapes, one precedence ladder, and one arrow-function escape hatch that swaps in the lexical scope on purpose. That same lexical scope is what powers closures, the sibling concept worth learning next.

If you remember one sentence, make it this: when this looks wrong, stop reading the function and go read the call. The object you’re missing was never in the definition. It was standing left of the dot, at a call-site somewhere else.

Frequently asked questions

What is the this keyword used for in JavaScript?

this is a reference to whatever object a function is currently acting on. It lets one function read or update different objects depending on how it is called, which is what makes methods, constructors, and class instances work without hardcoding the object name.

Can I use this inside an arrow function?

You can read this inside an arrow function, but an arrow function has no this of its own. It inherits this from the scope where it was written, so the call-site is ignored. That is exactly why arrow functions fix the classic "lost this" bug in callbacks, and also why you should not use one as an object method that needs this to point at the object.

Is this in JavaScript the same as this in Java?

No, and this trips up people coming from Java or C#. In Java, this always means the current instance, fixed by where the method is defined. In JavaScript, this is dynamic: it is decided by how the function is called, so the same function can see a different this on every call.

Why is this undefined in my function?

Almost always because the function was called standalone instead of as a method. In strict mode (which modules and classes use by default) a plain fn() call sets this to undefined. The usual cause is pulling a method off its object, like passing obj.method as a callback, which detaches it from the object.

How do I stop losing this in a callback?

Three fixes, in order of preference: use an arrow function so this stays lexical, define the method as a class field arrow so it is auto-bound to the instance, or explicitly bind it with fn.bind(obj). Any of the three keeps this pointing where you expect when the function runs later.

Share

Liked this? Let's build something.

I take on a small number of front end and AI-engineering projects, and I'm always up for talking shop. Tell me what you're working on.