Course outline · 0% complete

0/27 lessons0%

Course overview →

What is this, really?

lesson 2-1 · ~14 min · 4/27

That mechanism is a closure.

The returned function keeps a live link to the scope it was created in, so count stays alive after makeCounter has returned.

Keep the shape of that answer in mind, because it sets up a contrast that runs through this whole unit.

Closures are about where a function was written. Lexical scope is fixed by the source text, and no call site can change it.

this, today's topic, is about how a function was called. It is the one part of a function's environment that the source text does not decide.

Those two mechanisms answer different questions, and mixing them up is what makes this feel unpredictable. Once you know which rule applies, it stops being mysterious.

this is decided at call time

this is a special value available inside every regular function.

It exists so one function can serve many objects. A greet method written once must know which of a thousand users it was called on, and this is how it finds out.

Getting this wrong is one of the most common JavaScript bugs in production. Handlers that print undefined, methods that crash the moment they are passed around, callbacks that silently do nothing.

It is also a guaranteed interview topic, and the reason is that it rewards a precise model over memorized examples.

Here is the model. this is not attached to the function, it is decided fresh every time the function is called, by how it is called.

Rule 1, the method call. When you call a function through an object, as in obj.method(), this is the object before the dot.

That is the common case, and it is the one that makes methods work at all. A method uses this to reach the object it was called on, and the dot in the call site is what supplies it.

The object before the dot

The call site names the object.

const user = {
  name: "Ada",
  hello() {
    console.log("Hi, " + this.name);
  },
};

user.hello();

Output

Hi, Ada

In user.hello() the object before the dot is user, so inside the function this is user.

The function body never mentions user by name, which is the point. The same body would work for any object with a name, and that reuse is why this exists.

Assigning the same function to a second object would print that object's name instead, since nothing about the binding is baked in at definition time.

Bracket access follows the same rule. user["hello"]() also has user before the access, so this is still user.

Copying a method out of its object

Same function, no dot in the call.

const user = {
  name: "Ada",
  hello() {
    console.log("Hi, " + this.name);
  },
};

const detached = user.hello;
detached();

Output

Hi, undefined

this is no longer user, so this.name is undefined, and copying a method out of its object loses this.

Nothing was copied wrong. detached and user.hello are the exact same function object, and only the call site changed.

The failure is quiet, which is what makes it dangerous. It printed a wrong string rather than throwing, so the bug travels into output.

This is the shape of a very common real bug. Passing user.hello to setTimeout, to an event listener, or to array.map detaches it the same way, since the receiving code calls it without a dot.

In this example this is undefined in strict mode, and this.name on undefined would actually throw a TypeError. The undefined printed here reflects sloppy-mode script behavior, where this falls back to the global object, and the next block covers that split.

Rules 2 and 3

Rule 2, the plain call. Call a function with no object before the dot and there is nothing sensible for this to point at.

What you actually get depends on a language setting called strict mode, a stricter ruleset that code opts into by placing the string "use strict" at the top of a file or function.

Module files, meaning files that share code with import and export as unit 8 covers, are in strict mode automatically. So are class bodies, so effectively all modern code is strict.

In strict mode a plain call gets this = undefined. Only old-style scripts without it, which people call sloppy mode, silently fall back to the global object.

Either way it is not your object, which is why detached() broke.

Rule 3, arrow functions. Arrow functions have no this of their own.

They use the this of the surrounding scope, exactly the way a closure captures a variable back in lesson 1-2. An arrow does not get a new this at call time, because it never had a slot for one.

That makes arrows the right choice for a callback written inside a method. A callback is a function you hand to other code so it can be called later, like the function you pass to setTimeout, and the arrow keeps the method's this instead of losing it.

An arrow inside a method

The arrow inherits rather than receives.

const timer = {
  label: "tick",
  start() {
    const show = () => console.log(this.label);
    show();
  },
};

timer.start();

Output

tick

The arrow inside start has no own this, so it looks outward and finds start's this, which is timer.

Note that show() is a plain call with nothing before the dot. Rule 2 would give undefined for a regular function, and the arrow ignores rule 2 entirely.

Replacing the arrow with function () { ... } breaks it, which is the comparison worth running. The regular function gets its own this from the plain call and prints undefined.

The same fix works for real deferred calls. Writing setTimeout(() => console.log(this.label), 100) inside start still sees timer, even though the timer callback runs long after start returned.

This is the everyday reason arrows exist. Before them, methods opened with const self = this so a nested regular function could reach the object through a closed-over variable.

The flip side: arrows make bad methods

Rule 3 cuts both ways.

An arrow written directly as an object-literal method does not get the object as this, and the reason is that an object literal is not a scope.

Braces around an object literal look like a block, and they create no scope for variable lookup. So the arrow reaches past the object to the surrounding file's this, which is not your object.

In a module file that outer this is undefined, and in a browser script it is the global object. Neither one has your properties.

The working rule is short. Use shorthand syntax, as in good() { ... }, for methods, and arrows for callbacks inside them.

The same rule explains why arrows cannot be used as constructors or as prototype methods that expect a receiver, which unit 3 revisits once class syntax is on the table.

A method and an arrow side by side

Two properties, two different outcomes.

const counter = {
  count: 10,
  good() {
    console.log(this.count);
  },
  bad: () => {
    console.log(this && this.count);
  },
};

counter.good();
counter.bad();

Output

10
undefined

good is a normal method, so rule 1 applies and this is counter.

bad is an arrow, so it ignores the object entirely and inherits the file's this, and the count comes back undefined.

The this && this.count guard is there on purpose. Without it, a module-level this of undefined would make this.count throw a TypeError instead of printing.

Both properties were defined in the same object literal, in adjacent lines, which is what makes this a good interview question. The difference is entirely in the syntax used to write the function.

The failure does not depend on how bad is called. counter.bad() has a dot and an object, and the arrow still does not care, because rule 3 wins over rule 1 every time.

How is the function called?obj.fn()this = objfn()undefined / globalarrow () =>this from outer scopefn.call(x) / fn.bind(x)this = x (you choose · next lesson)
The this decision chart. First ask how the function is called. Arrows skip the question entirely and inherit this from where they were written.

Inside start, this is undefined.

f() is a plain call with no object before the dot, so rule 2 applies and strict mode gives undefined.

this is decided at call time rather than definition time, which is the whole reason the assignment matters. const f = timer.start copied only the function, not its owner.

The practical effect is a crash rather than a wrong value. this.label on undefined throws TypeError: Cannot read properties of undefined.

Note that the arrow inside start does not rescue anything here. It faithfully inherits whatever this its enclosing method got, and in this call that is undefined.

Fixing it takes a call-site change, either calling timer.start() with the dot or binding the receiver explicitly, which is exactly what the next lesson is about.