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:
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 inherit
from. 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);
Increments the balance
variable in Ron's bank account.
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()
method.
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.map(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 map
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.map(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
map
) doesn't have the correct value for this
. A named function
would fail for the same reason. Both would fail because it is called
(by .map
) 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 solutions1, 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.map(amt => this.deposit(amt));
}
...
}
That 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);
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