Menu

JavaScript this Keyword: Binding Rules and Common Pitfalls

How this actually works in JavaScript — the four binding rules, why arrow functions are different, and how to avoid the classic 'this is undefined' trap.

this Is Decided at Call Time

Most confusion around this comes from one false assumption: that this depends on where a function is defined. It doesn't. this depends on how the function is called.

Look at the same function called two ways:

index.js
Output
Click Run to see the output here.

Same function, different this. The first call has user to the left of the dot, so this is user. The second call has nothing to the left, so this is undefined. The function itself didn't change — the call site did.

Keep that idea in mind: to figure out this, look at the call, not the definition.

The Four Binding Rules

There are four ways this gets set. Almost every this-related question you'll ever have is answered by figuring out which one applies.

1. Method call: obj.fn()

When a function is called as a property of an object, this is that object:

index.js
Output
Click Run to see the output here.

The thing to the left of the dot becomes this. Simple as that.

2. Plain call: fn()

Called with no object in front, this is undefined in strict mode (which modules and classes enable automatically) or the global object in sloppy mode:

index.js
Output
Click Run to see the output here.

This is where the "this is undefined" errors come from. Pulling a method off its object turns a method call into a plain call:

index.js
Output
Click Run to see the output here.

No counter. before the call, no binding. The function doesn't remember the object it came from.

3. Explicit binding: .call(), .apply(), .bind()

You can force this to whatever you want:

index.js
Output
Click Run to see the output here.

.call and .apply call the function right away; they differ only in how arguments are passed. .bind returns a new function with this permanently locked — useful for callbacks.

4. new call: new Fn()

Calling a function with new creates a fresh object and binds it to this:

index.js
Output
Click Run to see the output here.

Classes use this mechanism under the hood. We'll meet them in a later chapter.

Arrow Functions Don't Have Their Own this

Arrow functions break the rules above, on purpose. They don't bind this at all — they inherit it from the surrounding scope, frozen at the moment the arrow is defined:

index.js
Output
Click Run to see the output here.

The arrow at the top level of a module captures the module's this, which is undefined. Even though it's called as user.arrow(), the arrow refuses to rebind.

That sounds like a bug, but it's the whole point. Arrow functions shine inside methods, where you want to keep the outer this:

index.js
Output
Click Run to see the output here.

The arrow inside setInterval inherits this from start, which was called as timer.start(). So this.seconds works. A regular function there would have gotten its own this (whatever setInterval passes in) and broken.

Rule of thumb: use arrow functions for callbacks inside methods. Use regular functions for methods themselves.

The Classic Callback Trap

This is the most common way this goes sideways. You pass a method as a callback, and it loses its binding:

index.js
Output
Click Run to see the output here.

setTimeout calls the function as a plain call, not as c.increment(). Three ways to fix it:

index.js
Output
Click Run to see the output here.

All three work. The arrow wrapper is usually the clearest.

this at the Top Level

The top-level this depends on where your code runs:

  • Browser script (non-module): this is window.
  • ES module (including most modern bundled code): this is undefined.
  • Node.js CommonJS module: this is module.exports.

For a reliable reference to the global object across environments, use globalThis:

index.js
Output
Click Run to see the output here.

You shouldn't lean on top-level this in practice. Reach for globalThis when you actually need the global object, and otherwise pass values around explicitly.

A Quick Decision Tree

When you see this and aren't sure what it'll be, work through this list:

  1. Is the function an arrow function? Then this is whatever it was in the enclosing scope. Ignore the call site entirely.
  2. Was it called with new? Then this is the new object.
  3. Was it called with .call, .apply, or a bound function? Then this is what was passed in.
  4. Was it called as obj.method()? Then this is obj.
  5. Was it called as a plain fn()? Then this is undefined in strict mode.

That ladder, in that order, decides every case.

Next: Higher-Order Functions

Now that this isn't a mystery, you're ready for the pattern that makes JavaScript so expressive: passing functions around as values. Next we'll look at higher-order functions — functions that take or return other functions — and how they power array methods, event handlers, and most real-world JavaScript code.

Frequently Asked Questions

What does this refer to in JavaScript?

this refers to the object a function is called on — not where the function was defined. In user.greet(), this is user. In a plain greet() call, this is undefined in strict mode (or the global object in sloppy mode). The rule is about the call site, not the definition site.

Why is this undefined inside my function?

You probably pulled a method off its object and called it standalone, or passed it as a callback. const fn = user.greet; fn(); loses the binding — there's no object to the left of the dot at the call site. Fix it with .bind(user), an arrow function wrapper, or by calling it as user.greet().

How is this different in arrow functions?

Arrow functions don't have their own this. They inherit it from the surrounding scope at the moment they're defined. That makes them ideal for callbacks inside methods, where you want to keep the outer this. It also means .call(), .apply(), and .bind() can't change this for an arrow function.

What is this at the top level of a script?

In a regular script in a browser, top-level this is the window object. In an ES module, it's undefined. In Node.js CommonJS modules, it's module.exports. The cross-environment reference is globalThis, which always points to the global object.

Learn to code with Coddy

GET STARTED