The THIS Keyword

As you know, the this keyword in JS OOP refers to the current object within a constructor or a method. In this reading, we'll look at a few surprising and subtle aspects of this, but we're not going to cover every quirk of it. If you want to know more about this you can explore more in that link.

Changeability

An important thing to keep in mind about this is that it's constantly changing. Every time there's a function call or a method call or an object construction, the value of this changes. Most of the time, the new value is either irrelevant or exactly what you want. So, there's no cause for alarm.

Constructor

If we use a constructor

var harry = new Account(50);

Then the value of this in the body of the constructor function is the new object (the one that will be assigned to harry above):

class Account {
    // our instance variable
    balance = 0;
    // the value of `this` is the new object
    constructor (init) {
        this.balance = init;
    }
}

That's exactly what we want.

Methods

If we use a method

harry.deposit(100);

then the value of this in the body of the method is the object that the method is operating on:

class Account {
    // our instance variable
    balance = 0;
    constructor (init) {
        this.balance = init;
    }
    // the value of `this` is the object to work with
    deposit (amt) {
        this.balance += amt;
    }
}

Again, this is exactly what we want.

Methods are Functions

So far, we've only invoked methods. The dot syntax, though, allows us to pull a value out of an object, and a method is a value in an object (kinda — there's a subtlety here that we can skip for now). Return to our bank page and try the following code:

harry.balance;
harry.deposit;

Here's a screenshot of how it worked for me:

harry balance and deposit

So, deposit is a kind of function, but a function stored in an object. Actually, to be precise, the function is stored in something called a prototype, which all instances of a class have access to. That means that Harry's deposit method is the very same function as Ron's and Hermione's deposit methods. But prototypes are not a source of concern.

Stripped to its essentials, the deposit method (all of them; they're all the same) is:

function (amt) {
    this.balance += amt;
}

So, it will work fine as long as there's a value for this.

As you know, when we use the method properly, the value of this is set correctly:

ron.deposit(10);

Remember, a method is just a function that belongs to a particular class of object, like deposit and withdrawal belong to Account.

Methods as Arguments

But we know from earlier work (such as the Plotting assignment) that functions can also be arguments, as with the .map() and .forEach() methods.

Suppose a group of friends decides to help Ron out, passing the hat to collect money for him (maybe to go home for the holidays or to get a new broom or dress robes). We could implement code like this:

console.log(ron.balance);
var hat = [10, 20, 30, 40]; // total of 100
hat.forEach(function (amt) { ron.deposit(amt) });
console.log(ron.balance);

This works spectacularly well. (You can copy/paste that code into the console for the bank page and test it. Ron's balance increases by 100.)

But note that the argument to forEach above is an ordinary function, not a method. What if it were a method? Try the following:

console.log(ron.balance);
var hat = [10, 20, 30, 40]; // total of 100
hat.forEach(ron.deposit);
console.log(ron.balance);

That fails. It fails because the value of this is incorrect. But the earlier code with the anonymous function works fine, so let's stick with anonymous functions.

Functions inside Methods

In fact, we decide to get ambitious. We decide that the passing the hat code is useful and generic, and it might be nice to have a method that makes a bunch of deposits into a bank account. Here's our attempt:

class Account {
    ...
    depositAll (hat) {
        hat.forEach(function (amt) { this.deposit(amt) });
    }
    ...
}

Try it out at account2 with code like:

console.log('before', ron.balance); 
ron.depositAll([1,2,3,4]);
console.log('after', ron.balance);

The code fails because the anonymous function (the argument to forEach) doesn't have the correct value for this. A named function would fail for the same reason. Both would fail because the function is called (by .forEach) as an ordinary function, not as a method, and ordinary functions get a different value for this. (The value it gets varies depending on JavaScript implementation and context, but it's never the right value. In a browser, it's the global object, which we met as globalThis.)

Arrow Functions as a Solution

There are many solutions, but the easiest is to use an arrow function instead of a normal function.

Remember that an arrow function, in it's most concise form, consists of a parameter list, an arrow, and an expression. For example:

xValues = [1, 2, 3, 4];
doubles = xValues.map(function (x) { return x*2; });
triples = xValues.map( x => x*3 );

You can see that the arrow function is much more concise. (If we have multiple parameters, we use parentheses around them, and if we have a multi-statement function body, we surround it with braces and bring back the return keyword, if necessary.)

When we learned about arrow functions, we only learned about them as a concise alternative to normal functions, but there also are a few technical differences between them and normal functions:

  • Invoking a normal function changes this
  • Invoking an arrow function does not change this

Note that the second fact means that we cannot use arrow functions as methods. But we can use them inside methods! So, we can change the depositAll method to the following:

class Account {
    ...
    depositAll (hat) {
        hat.forEach(amt => this.deposit(amt));
    }
    ...
}

It works! The this inside the arrow function is the same value it normally has in the method, which means it still correctly refers to the bank account.

Try it out at account3 with code like:

console.log('before', ron.balance); 
ron.depositAll([1,2,3,4]);
console.log('after', ron.balance);

Other Solutions

The arrow function solution is simple and effective. It's really all you need. However, before we drop the subject, let's look at two other ways to solve it: bind and closures. (If your brain is full, you can stop here. This section is optional.)

For the purpose of this problem discussion, let's use our bank account example as a concrete case, and then we'll describe it more generally. You can try the example code using

account4.html

The depositAll method takes an array as an argument (a "hat") and deposits all the amounts in the hat into the given bank account. Pretty cool, but a little inflexible. Suppose we wanted a function, taking one argument (an amount of money) and invoking the function deposits that money in the given bank account. Let's call that a depositor function.

Arrow functions are one way to create a depositor function:

const d1 = amt => ron.deposit(amt);
d1(10); // ron is 10 galleons richer

So, as we said, an arrow function solves the problem. Remember that the function/method itself does not solve the problem:

const d2 = ron.deposit;
d2(10);  // gets an error

which results in the following error:

screeenshot of d2 failing

The failure is because the value of this is undefined or unbound, so I'm going to call the problem the "unbound this" problem.

Another solution is to use the bind method, which specifies a value to use for this. Consider the following, which works:

const d3 = ron.deposit.bind(ron);
d3(30);   // ron is 30 galleons better off

That may seem redundant, but remember that all the methods live in the prototype object which is accessible from the class constructor function, so the following means exactly the same thing but doesn't look as confusing:

const d4 = Account.prototype.deposit.bind(ron);
d4(40);

In short, the bind method takes a method (like deposit), which can work for any object (of the right class) by operating on the this keyword, and returns a new function that has a specified, fixed (constant) value for this. In the example above, d4 is a new function that will always deposit into Ron's account.

Prior to bind and arrow functions being introduced into the JS language, programmers ran into the "unbound this" problem. They solved it by using closures. Basically, the value of this constantly changes, so they copied the value they wanted to a new variable, and returned a closure over that value. Traditionally, the variable was named that.

Before showing you the closure solution, I'm going to define a variant of our Account class that has methods that return depositor functions. Remember, the depositor function is a pure function (not a method) that we could, for example, use with forEach or map or just by themselves, as we did with d1, d3 and d4 above.

The complete code is in account4.js and you can try it in account4.html. Here's an excerpt:

class Account {
    // instance variables
    balance = 0;

    constructor (init) {
        this.balance = init;
    }
    deposit (amount) {
        this.balance += amount;
    }
    // the depositAll method works
    depositAll (hat) {
        hat.forEach(amt => this.deposit(amt));
    }
    // return a depositor function
    getDepositor_arrow() {
        return amt => this.deposit(amt);
    }
    getDepositor_bind() {
        return this.deposit.bind(this)
    }
    getDepositor_that() {
        let that = this;
        return function (amt) { that.deposit(amt) }
    }

} // end class

Let's try it out. The solution using arrow functions works:

d5 = ron.getDepositor_arrow();
d5(50);  // 50 galleons richer

The solution using .bind also works:

d6 = ron.getDepositor_bind();
d6(60);

Note that the code for depositor_bind is a little confusing, because there are two uses of this:

    getDepositor_bind() {
        return this.deposit.bind(this)
    }

The first retrieves the method from the prototype, and the second is the argument to bind. Since the latter is the same as ron, the depositor function deposits into Ron's account.

Finally, there's a solution that uses a closure over that. It also works:

d7 = ron.getDepositor_that();
d7(70);  // richer and richer

The code is simple but not obvious:

    getDepositor_that() {
        let that = this;
        return function (amt) { that.deposit(amt) }
    }

We copy the value of this to a new variable, that and then return a closure over that variable. Skipping the copy and just returning a function referring to this would not work, as we saw near the beginning of this reading. (The first time I saw that used as we did here, I was quite baffled. Fortunately, I understood about closures, so I was able to figure it out.)

Which technique should you use? Let's briefly summarize:

  • arrow: yes, you should use this. It's simple and effective.
  • bind: you can use this. It has the advantage about being clear about what's going on, namely that you are binding the value of this. But you should probably stick to the arrow function.
  • that: this is interesting to know about, and you might encounter it in old (pre-2009) code, but otherwise it's only for historical interest.

Summary

  • Most of the time, this has exactly the value you need
  • Anonymous functions inside methods might not have the correct value for this
  • Invoking an arrow function doesn't change the value of this so they are a better choice inside a method
  • Arrow functions cannot be methods, because invoking them doesn't change the value of this