RizTech Academy logo
RizTech Academy
Functions, Scope and ClosuresLesson 4 of 530 min

The this keyword, and why it keeps changing

Every other name in JavaScript means what the place it was written says it means. That is lexical scope, and two lessons have now relied on it. this is the exception, and that exception is why it confuses people who have been writing JavaScript for years.

The one rule

this is decided by how a function is called, not by where it was written.

The same function, called two ways, gets two different this values. That is the whole difficulty, and once you accept it the rest is a short list of cases.

Case 1: called as a method

const order = {
  plates: 3,
  rate: 80,
  total() {
    return this.plates * this.rate;
  },
};

console.log(order.total());
240

The function was called as order.total(), so this is order. Look at what is immediately to the left of the dot — that is this. This covers most of your everyday use and is entirely unsurprising.

Case 2: the same function, detached

Now pull it off the object and call it on its own:

const total = order.total;
console.log(total());
TypeError: Cannot read properties of undefined (reading 'plates')

Identical function. Different call. Nothing to the left of a dot, so this is undefined, and undefined.plates throws.

This is not a contrived move. It is what happens every time you pass a method somewhere:

setTimeout(order.total, 1000);
button.addEventListener('click', order.total);

Both of those hand the function over, detached from order. When it runs, the object is long forgotten.

One caveat worth knowing: in old-style non-strict code, this in a detached call is the global object rather than undefined, so instead of an error you get NaN — a wrong answer instead of a crash. Modules and classes are always strict, so you will normally get the error, which is better.

Case 3: lost inside a callback

The commonest real form of the bug:

const shop = {
  name: 'Anna Tiffins',
  items: ['dal', 'rice'],
  list() {
    return this.items.map(function (item) {
      return `${item} from ${this.name}`;
    });
  },
};

console.log(shop.list());
TypeError: Cannot read properties of undefined (reading 'name')

Read it carefully. this.items worked — we got into map — so this was shop at the top of list. But the function passed to map is called by map, not by you, and not with a dot. So inside it this is undefined.

this changed halfway down a single function. Nothing else in JavaScript does that.

The fix: arrow functions

An arrow function has no this of its own. It uses whatever this meant in the place it was written — which is ordinary lexical scope, finally.

const shop = {
  name: 'Anna Tiffins',
  items: ['dal', 'rice'],
  list() {
    return this.items.map((item) => `${item} from ${this.name}`);
  },
};

console.log(shop.list());
[ 'dal from Anna Tiffins', 'rice from Anna Tiffins' ]

The arrow was written inside list, where this is shop, so that is what it uses. It cannot be detached from it, because it never had its own to lose.

This is what arrow functions were added for. The shorter syntax is a bonus.

The mirror image: never an arrow as a method

Because an arrow takes this from where it was written, writing one directly on an object is exactly wrong:

const order = {
  plates: 3,
  rate: 80,
  total: () => this.plates * this.rate,
};

console.log(order.total());
TypeError: Cannot read properties of undefined (reading 'plates')

The arrow was written at the top level, not inside anything, so its this is whatever the top level's is — undefined in a module. It is not order, and no way of calling it will make it order.

So: arrow functions for callbacks, ordinary functions for methods. The rule from the first lesson of this module, now with its reason.

Forcing it: call, apply and bind

Three ways to set this explicitly.

const order = { plates: 3, rate: 80 };

function total(discount) {
  return this.plates * this.rate - discount;
}

console.log(total.call(order, 40));
console.log(total.apply(order, [40]));

const orderTotal = total.bind(order);
console.log(orderTotal(40));
200
200
200
Method What it does
fn.call(thisValue, a, b) Calls it now, arguments listed.
fn.apply(thisValue, [a, b]) Calls it now, arguments in an array.
fn.bind(thisValue) Returns a new function permanently tied to that this. Does not call it.

bind is the useful one, and it is the pre-arrow solution to case 2:

setTimeout(order.total.bind(order), 1000);

You will see bind throughout older code and React class components. In new code an arrow usually reads better.

The full picture

How it is called What this is
obj.method() obj — whatever is left of the dot
fn() standalone undefined in strict mode; the global object otherwise
Arrow function Whatever this was where it was written
fn.call(x) / fn.apply(x) / fn.bind(x) x
new Fn() The newly created object — module 5
DOM event handler The element the listener is attached to — module 7

The DOM row is why event handlers deserve care: an ordinary function handler gets the element as this, which is occasionally handy and usually not what you meant. Module 7 uses event.currentTarget instead, which says what it means.

When to avoid the question

Honest advice to finish with: a great deal of good JavaScript uses this hardly at all.

If a function needs some data, pass the data in. Closures, from the last lesson, give you private state with no this anywhere. this becomes genuinely necessary with classes, in module 5, and when working with libraries that expect it.

So learn the rules — you need them to read other people's code and to survive interviews — and then write code that does not lean on them.

Check your work

order.total() is 240. Called with order to the left of the dot, so this is order.

const total = order.total; total(); gives TypeError: Cannot read properties of undefined (reading 'plates'). Same function, no dot, so this is undefined in strict mode. In non-strict code you would get NaN instead — a wrong answer rather than an error.

Inside map(function (item) {...}), this is undefined even though this.items worked one line earlier. map calls the function, and not as a method. This is this changing part-way through one function.

The arrow version works, giving [ 'dal from Anna Tiffins', 'rice from Anna Tiffins' ], because an arrow has no this of its own and uses the one from where it was written.

An arrow used as a method fails, because it takes this from the top level rather than from the object. No way of calling it fixes that.

call, apply and bind all give 200 for 3 × 80 − 40. call takes arguments listed, apply takes them in an array, and bind returns a new function rather than calling it.

call cannot rescue an arrow either. arrowMethod.total.call(order) fails with the same error. An arrow's this is fixed at the point it was written, and nothing can reassign it.

Re-binding a bound function does nothing. bound.bind(other) returns a function that still uses the first binding — with o at 3 plates and other at 10, it still gives 240, not 1000. The first bind wins permanently.

Which to use for a callback? An arrow. For an object method? An ordinary function. To hand a method to setTimeout without an arrow? bind.

Practice

  1. Build the order object with a total() method and call it. Confirm 240.
  2. Detach it. Assign order.total to a variable, call it, and read the error. Say which part of the message tells you this was undefined.
  3. Fix the detached call three ways: call, bind, and wrapping it in an arrow.
  4. Reproduce the callback bug. Write shop.list() with an ordinary function inside map and watch it fail after this.items succeeded. This is the one worth dwelling on — this changed inside a single function.
  5. Fix it with an arrow. Then explain, in one sentence, why the arrow does not have the problem.
  6. Do the mirror image. Write a method as an arrow directly on an object and watch it fail. Confirm that call cannot rescue it either — an arrow's this cannot be set.
  7. Write a function using this.plates and call it with call, then apply, then a bound copy. Confirm all three agree.
  8. Take a bound function and try to re-bind it to a different object. Note what happens — binding is permanent.
  9. Harder. Rewrite the shop object so it works with no this at all, using a closure instead. Compare the two versions and decide which you would rather hand to somebody else to maintain.

Next: callbacks and higher-order functions — passing functions around as values, which is what map was doing all through this lesson.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship