Articles

JavaScript's this Keyword, Explained

How JavaScript determines what this refers to: default, implicit, explicit, and new binding, plus why arrow functions behave differently.

Takina Takina · · 4 min read
A laptop displaying JavaScript code

In JavaScript, this is a keyword that refers to the object a function is currently executing on — but unlike most languages, that object isn’t fixed by where the function is defined. It’s determined by how the function is called, which is exactly why this trips up so many developers coming from other languages.

There’s no single rule for what this means. Instead there are four binding rules, checked roughly in priority order, plus a fifth case for arrow functions that opts out of the whole system.

The four binding rules

Default binding. Call a plain function on its own, and this falls back to the global object (window in browsers) in non-strict mode, or undefined in strict mode (which ES modules and class bodies use by default).

function whoAmI() {
  console.log(this);
}
whoAmI(); // undefined in strict mode

Implicit binding. Call a function as a method on an object, and this becomes that object.

const user = {
  name: "Ava",
  greet() {
    console.log(this.name);
  },
};
user.greet(); // "Ava"

Explicit binding. Force this to a specific value with call, apply, or bind.

function greet() {
  console.log(this.name);
}
greet.call({ name: "Priya" }); // "Priya"

bind is the odd one out: instead of invoking the function, it returns a new function permanently bound to the given this, ignoring any later implicit or default binding attempts.

new binding. Call a function with new, and JavaScript creates a fresh object, sets this to it, and returns it (unless the constructor explicitly returns another object).

function Person(name) {
  this.name = name;
}
const p = new Person("Sam");

When more than one rule could apply, new wins, then explicit binding, then implicit, then default. Knowing that priority order resolves most “wait, what is this here” confusion.

Arrow functions don’t have their own this

Arrow functions are the exception to all four rules above: they don’t bind this at all. Instead, they capture this lexically — from the enclosing scope at the point they’re defined, the same way closures capture variables.

const timer = {
  seconds: 0,
  start() {
    setInterval(() => {
      this.seconds++; // `this` is `timer`, inherited from start()
    }, 1000);
  },
};

Had that callback been a regular function, this inside setInterval would fall back to the global object (or undefined in strict mode), because setInterval calls the callback with no receiver — a classic instance of implicit binding being lost.

Where this gets lost

The most common this bug is passing a method as a callback and losing its receiver:

class Counter {
  count = 0;
  increment() {
    this.count++;
  }
}
const counter = new Counter();
button.addEventListener("click", counter.increment); // this is undefined inside increment

counter.increment is passed as a bare function reference. By the time the event fires, there’s no object to the left of the call — default binding kicks in, and this is undefined (in a class, which is always strict mode).

Three common fixes:

FixHow it works
Arrow class fieldincrement = () => { this.count++; } — captures this from the constructor’s scope at definition time
.bind() in the constructorthis.increment = this.increment.bind(this) — permanently binds before the method is ever detached
Wrap at the call siteaddEventListener("click", () => counter.increment()) — implicit binding happens inside the arrow function

Arrow class fields are the most common modern fix because they need no constructor boilerplate, though each instance gets its own copy of the function rather than sharing one on the prototype.

this in different contexts

this behaves differently depending on where it appears:

  • Top-level module codeundefined in ES modules (all modules run in strict mode).
  • Regular function, called plainlyundefined in strict mode, global object otherwise.
  • Object method — the object left of the dot at call time.
  • Class method — the instance, when called as instance.method().
  • Arrow function — inherited lexically, never bound by the call.
  • Event handler (addEventListener) — the DOM element the listener is attached to, unless it’s an arrow function.

That last one is worth calling out: browser APIs like addEventListener explicitly set this to the element for a regular function handler, which is a form of implicit binding you didn’t write yourself.

Understanding this also matters once you start writing async code, since a this-losing callback bug is just as easy to introduce inside a .then() handler as inside setTimeout. If you’re moving to TypeScript, the compiler can catch some of these mistakes — strict mode flags an implicit any on this in some configurations — but it doesn’t rewrite the runtime binding rules, so the mental model above still applies.

The takeaway

this is resolved at call time, not definition time, by checking four rules in order: new, explicit (call/apply/bind), implicit (method call), and default (plain call). Arrow functions skip all of that and inherit this from their enclosing scope instead. Most real-world this bugs come from detaching a method from its object — passing obj.method as a callback — so when you hand a function off to something else to invoke, either use an arrow function, bind it explicitly, or wrap it at the call site.

Takina Takina · · 5 min read

Promise.all() vs allSettled() vs race() Compared

Promise.all() fails fast, allSettled() waits for every result, and race() returns whichever promise finishes first — how to choose correctly.

#JavaScript #Web Development #Frontend
Takina Takina · · 4 min read

JavaScript Spread vs Rest Operators, Explained

The spread operator (...) expands an iterable into individual elements; the rest operator collects elements back into an array. Same syntax, opposite jobs.

#JavaScript #Web Development #Frontend
Takina Takina · · 4 min read

ResizeObserver API Explained: Watching Element Size

The ResizeObserver API lets JavaScript watch an element's box size and react without polling or resize-event hacks. How it works and when to use it.

#Web Development #JavaScript #Frontend