Command Palette

Search for a command to run...

Hectal
PHASE 7Intermediate ~28 min· topic 1 of 8

Topic 7.1

The this Keyword

In one line

this is a hidden extra input to every regular function, and its value is decided by how the function is called, not where it was written; arrow functions are the exception, because they have no this of their own and use the one around them.

Think of it like this

The word "my". When Asha says "my bag", she means Asha's bag. When Ravi says the exact same words, "my bag" means Ravi's bag. The sentence doesn't change; who says it decides what "my" points to. In JavaScript, this is the word "my": the same function code works on a different object depending on who calls it.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

this
A hidden input to a regular function: the object the function is working on for this particular call.
Call site
The exact place in the code where a function is called, like asha.sayName(). It decides this.
Method
A function stored as a property of an object, called with object.method().
Binding
Connecting this to a particular object for a call.
Strict mode
A safer set of rules, turned on with "use strict" and always on in classes and modules. A plain call gets this = undefined.
Sloppy mode
The old default rules for scripts without "use strict". A plain call gets this = the global object.
Global object
The object holding global names. In a browser page it is window; globalThis names it in every environment.
Lexical this
How arrow functions work: they use the this of the code around them instead of getting their own.
Callback
A function you hand to other code (like setTimeout or map) so that code can call it later.

Step by step

01One function, two owners

Write one function that uses this, and put it in two objects. The function exists once; both objects hold a reference to it.

asha.sayName() and ravi.sayName() run the same code, but the object before the dot is different, so this is different.

this.jswhole filejavascript
"use strict";
function sayName() {
  return "I am " + this.name;
}
const asha = { name: "Asha", sayName };
const ravi = { name: "Ravi", sayName };
asha.sayName(); // "I am Asha"
ravi.sayName(); // "I am Ravi"

02Call it without an object

sayName() on its own has nothing before the dot. In strict mode this is undefined, so reading this.name throws a TypeError. That error is helpful: it tells you that you lost this.

Without "use strict", this would be window, and this.name would quietly give window.name (usually an empty string). No error, just a wrong answer.

terminal
$ sayName()
── expected output ──
Uncaught TypeError: Cannot read properties of undefined (reading 'name')

03Find this with four questions

Ask these questions about the call, in this order. The first "yes" wins. Arrow functions skip the whole list: they use the this from the code around them.

Find this with four questionsdiagram
Rendering diagram…

04Lose this by passing a method

setTimeout(timer.report, 0) doesn't call report; it hands the bare function to the browser. When the timer fires, the browser calls it without your object. (Browsers call timer callbacks with this set to window.)

Wrap it in an arrow so the dot is back at the moment of the call: setTimeout(() => timer.report(), 0).

timer.jswhole filejavascript
setTimeout(timer.report, 0);          // this is window: the object is lost
setTimeout(() => timer.report(), 0);  // this is timer: called with a dot

05Use arrows inside methods, not as methods

Inside a method, this is the object. An arrow callback written there borrows that this, so this.name works inside map.

A function callback inside map is called plainly by map, so it gets its own this (undefined in strict mode). Before arrows (ES2015), people wrote const self = this; to work around this.

team.jswhole filejavascript
const team = {
  name: "Rockets",
  members: ["Asha", "Ravi"],
  list() {
    return this.members.map((m) => m + " of " + this.name);
  },
};

Try it yourself

  1. 1

    Turn strict mode off

    In the first example, delete the "use strict"; line. Predict the third line before you run it. (Hint: what is this in a sloppy plain call, and what is window.name in this preview?)

  2. 2

    Rescue the loose function

    In the second example, change const loose = timer.report; to const loose = () => timer.report();. Which line of the output changes, and why?

  3. 3

    Fix the function callback the old way

    In the third example, add const self = this; as the first line of listWithFunction, and use self.name inside the callback. Predict the second line. This is how code was written before arrow functions.

Code & diagrams

The call site decides this New tab

Both objects hold the very same function. Only the call changes `this`. Remove `"use strict"` and the third line changes, because sloppy mode gives `window` instead.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

I am Asha
I am Ravi
this is undefined
Same function? true
Losing this and getting it back New tab

`loose()` is a plain call, so strict mode gives `undefined`. The browser calls timer callbacks with `this` set to `window`. The arrow and `bind` both keep the timer.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

this: Tea timer
this: undefined
this: window
this: Tea timer
this: Tea timer
Arrow functions borrow this ES2015+ New tab

The arrow inside `listWithArrow` uses the method's `this`. `map` calls the `function` callback plainly, so it gets `undefined`. The arrow method was written at the top level of the script, where `this` is `window`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

[ 'Asha of Rockets', 'Ravi of Rockets' ]
[ 'Asha of ???', 'Ravi of ???' ]
Arrow method sees window, not team: true
this in event listeners New tab

A `function` listener gets the element it is attached to as `this`. An arrow listener keeps the outside `this`; use `event.currentTarget` there instead (Phase 9).

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

function listener, this is <button#save>
arrow listener, this is window? true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Pass a method as a click handler

Write const counter = { count: 0, increment() { this.count++; out.textContent = this.count; } }; then button.addEventListener("click", counter.increment); and click the button.

terminal
$ the page shows
── what you'll see ──
NaN

Break #2

Use an arrow function as a method

In a module or strict file, write const user = { name: "Asha", hi: () => "Hi " + this.name }; and call user.hi().

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot read properties of undefined (reading 'name')

Myth vs fact

Myth

this is the object the function was written in.

Fact

For regular functions, this is decided at call time by how the function is called. The same function can see a different this on every call. Only arrow functions keep the this from where they were written.

Myth

this refers to the function itself.

Fact

Never. Inside function f() {}, this is not f. To refer to the function, use its name.

Myth

Arrow functions are always the better choice because they fix this.

Fact

They're the right choice for callbacks inside methods. As object methods, prototype methods or constructors they're wrong: they can't see the object and can't be used with new.

Myth

Classes make this safe.

Fact

Class methods still lose this when passed as callbacks. Classes only make the failure louder, because class code is strict and you get undefined instead of window.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The spec models this as part of a Reference: evaluating obj.method produces a reference with a base value (obj), and calling it uses that base as this. That is why (obj.method)() keeps this (the brackets don't remove the reference) while (0, obj.method)() or (x = obj.method)() lose it: the comma and assignment operators produce a plain value. Bundlers emit (0, fn)() on purpose to call a function without a receiver.

  • ▸

    In sloppy mode a primitive this is boxed: function f() { return typeof this; } f.call(5) returns "object" (a Number wrapper). In strict mode it stays "number". This is one reason strict mode, which classes and modules turn on automatically, is the norm.

  • ▸

    The HTML standard says timer callbacks (setTimeout, setInterval) are called with this set to the WindowProxy, even when the callback is strict code. Event listeners get event.currentTarget. Array methods like map, forEach and filter take an optional second argument, thisArg, which becomes this for a function callback: arr.map(function () { ... }, obj).

  • ▸

    A method called through optional chaining keeps its receiver: obj?.method() and obj.method?.() both call with this = obj. Destructuring does not: const { method } = obj; method(); is a plain call.

Remember this

  1. 1

    this is a special name available inside every regular function (one written with function or as a method in an object or class). It is not a normal variable you declare: the engine fills it in each time the function is called. That moment is called the call site: the place in the code where the function is called, like asha.sayName(). To find this, don't look at where the function was written; look at how it was called.

  2. 2

    Rule 1, method call: when you call a function as object.method() (with a dot or brackets right before the brackets that call it), this is the object before the dot. So in asha.sayName(), this is asha; in ravi.sayName() it is ravi, even if both objects hold the very same function. This is called implicit binding: binding means "connecting this to an object".

  3. 3

    Rule 2, plain call: when you call a function on its own, as sayName(), there is no object before the dot. In strict mode (turned on by "use strict" at the top of a file, and always on inside classes and modules) this is undefined. In old sloppy mode (normal scripts without "use strict") this falls back to the global object (window in a browser). The sloppy fallback hides bugs: this.name quietly reads window.name instead of throwing.

  4. 4

    **Losing this:** the dot only counts at the moment of the call. const f = asha.sayName; f(); copies the function out of the object, and the later call f() is a plain call, so this is undefined (strict) or window (sloppy). The same thing happens when you pass a method somewhere else to be called later: setTimeout(timer.report, 100) or button.addEventListener("click", counter.increment). The receiver calls it its own way, and the link to your object is gone. You fix it with an arrow wrapper (() => timer.report()) or bind (Topic 7.2).

  5. 5

    Arrow functions (() => ...) don't have their own this. They use the this of the code around them at the place they were written, like any outside variable (this is called **lexical this). That makes arrows perfect inside** methods, for callbacks like this.items.map((x) => ... this.name ...), and wrong as methods: { name: "Asha", hi: () => this.name } doesn't see the object, because an object literal { } is not a function and creates no this.

  6. 6

    Two more rules complete the picture. **new** (Topic 7.4) makes this a brand-new object. **call, apply and bind** (Topic 7.2) let you choose this yourself. When rules compete, the order is: new first, then bind/call/apply, then the method call, then the plain call. Some places set this for you: at the top level of a classic script it is window, at the top level of a module it is undefined, and in an event listener written with function it is the element the listener is on (you'll meet this again in Phase 9).

Explain it without notes

01

How is the value of this decided inside a regular function?

02

Why does passing obj.method to setTimeout or addEventListener lose this, and how do you fix it?

03

How do arrow functions handle this, and when is that a problem?

04

What is this at the top level of a classic script, of a module, and inside a plain function call in strict and sloppy mode?

Practice

01

Predict the four lines this prints, then run it: "use strict"; const cat = { name: "Milo", speak() { return this?.name ?? "nobody"; } }; const dog = { name: "Rex", speak: cat.speak }; const s = cat.speak; console.log(cat.speak()); console.log(dog.speak()); console.log(s()); console.log((0, cat.speak)());

02

A stopwatch object has seconds: 0 and a start() method that should add 1 to this.seconds every second with setInterval. Write start() so this works, and say why a function callback would fail.

03

Write an object greeter with a names array and a greetAll() method that returns an array of "Hello, <name> from <greeter.place>" strings, reading place through this inside the map callback.

Trade-offs

  • ↔

    Dynamic this lets one function serve many objects (that's how prototype methods are shared), but it means a method can be detached and break; lexical this (arrows) never breaks, but can't be shared as a method.

  • ↔

    Avoiding this entirely (closures and plain functions, Topic 7.8) removes this whole class of bugs, at the cost of the memory sharing that prototypes and classes give you.

Done when you can

  • I can find this by looking at the call site, not the definition.

  • I can apply the four rules in order: new, explicit, method call, plain call.

  • I can explain why a method passed as a callback loses this, and fix it two ways.

  • I know arrow functions use the surrounding this and are wrong as methods.

  • I know what this is in strict and sloppy mode, in scripts, modules and event listeners.