Understanding ‘this’ keyword in JavaScript

SungTheCoder
sungthecoder
Published in
6 min readMay 25, 2016

If you are coming to JavaScript world from classical OO languages like Java or C++, you’d be very happy to see ‘class’ and ‘this’ keyword in the language. But before you jump into writing Java looking JavaScript code, you should know that JavaScript class is not the same class you know before. The meaning of this is different in JavaScript.

First, let’s take a look at the code example:

class Timer{
constructor(second){
this._second = second;
document.getElementById(‘timer’).innerHTML = second;
}
_process(){
this._second -= 1;
document.getElementById(‘timer’).innerHTML = this._second;
if (this._second <= 0){
this.stop();
}
}
start() {
this._interval = setInterval(function doSomething(){
this._process();
}, 1000);
}
stop() {
clearInterval(this._interval);
}
}
var t1 = new Timer(10);
t1.start();

Above code declare Timer class. The Timer class has a constructor and 2 public methods, start() and stop(). The constructor accepts a number as a parameter.

And it creates a Timer object and call start() method. The start() method set interval to call _process() function every 1000 milliseconds.

But if you run this code, you will instead see nothing happening in the browser and ‘Uncaught Type Error: this_process is not a function’ in your development console.

This error is from start() method. The line with this._process() is the problem. this object does not have _process method. If you use debugger, or add console.log(this); right above the this._process();, you would see that this is global object.

console.log(this) shows Window object

Why is that happening? Should this be the Timer object? To understand this phenomenon, you need to understand how this works in JavaScript.

How to determine the meaning of ‘this

Unlike classical OO languages, this does not mean object itself. this in JavaScript is more of call site. I’ll explain what the call site means later. When JavaScript sees this keyword, it asks these three questions to determine the value of this in the same order as below.

  1. Is the function called with new keyword?
  2. Is the function called with explicit binding?
  3. Is the function called with containing object (implicit binding)?
  4. Then it is the default value: global or undefined in strict mode.

Let’s take a look at each case.

Default: Global Object or Undefined

When a function is called standalone, this binds to global object.

function whatIsThis(){
console.log(this);
}
whatIsThis();

The above code will show window object on console.

this is global object

But if your code is running in strict mode, this will be undefined.

Implicit Binding

If you are calling a function as a method of an object, this will bind to the object that contains the function.

function sayName(){
console.log(this._name);
}
var foo = {
_name: "Foo",
sayName: sayName
}
var bar = {
_name: "Bar",
sayName: sayName
}
foo.sayName();
// Foo
bar.sayName();
// Bar
sayName();
// undefined

As seen on the code above, the value of this is dynamically determined. When sayName() function is called within foo object, this binds to foo object. But the value of this becomes bar object when the function is called within bar object. So the value of this depends on where it was called from. You can think this as a call site.

When sayName() is called without any object, this becomes global object (or undefined if it is strict mode).

Explicit binding

You can force the value of this with .call() or .apply() function. Or you can create a function that always binds to a certain object with .bind() function. When you call a function with .call(thisObj) or .apply(thisObj), the value of this inside of the function will be thisObj. Let’s take a look at the code:

function sayName(){
console.log(this._name);
}
var foo = {
_name: "Foo",
sayName: sayName
}
var bar = {
_name: "Bar",
sayName: sayName
}
sayName.call(bar);
// Bar
sayName.call(foo);
// Foo
foo.sayName.call(bar);
// Bar
bar.sayName.call(foo);
// Foo

Note that in the last line of the code, even when sayName is called within bar object, .call(foo) override the value of this to be foo.

If you want to create a function that always bind this to a certain object, use .bind() function.

function sayName(){
console.log(this._name);
}
var foo = {
_name: “Foo”,
}
var sayFoo = sayName.bind(foo);var bar = {
_name: “Bar”,
sayName: sayName,
sayFoo: sayFoo
}
sayFoo();
// Foo
bar.sayFoo();
// Foo
sayFoo.call(bar);
// Foo
bar.sayName();
// Bar

sayFoo() function is newly created function based on sayName. But it was created with .bind() method. So this value in sayFoo will always be foo object.

When sayFoo is called within bar object, the value of this stays as foo object. Even when it is called with .call(bar) function, this still is foo.

Function or Class with new keyword

When a function is called with new keyword, it does following things:

  1. Creates an empty object {}
  2. Binds this to the newly created empty object
  3. Runs the function or constructor
  4. Returns the newly created object

Let’s look at the example of function and class definition.

function Beer(alcohol) {
this._happy = true;
this._focus = false;
this._alcoholContent = alcohol;
}

or

class Beer{
constructor(alcohol) {
this._happy = true;
this._focus = false;
this._alcoholContent = alcohol;
}
}

When you call the Beer function with new object like this:

var beer = new Beer(5);

It will (1)create an empty object, (2)fill the object with _happy, _focus, and _alcoholContent properties and (3)return the newly created object. The returned object will look like this:

{
_happy: true,
_focus: false,
_alcoholContent: 5
}

Back to our Timer problem

class Timer{
/* ... omitted ... */
start() {
this._interval = setInterval(function doSomething(){
this._process();
}, 1000);
}
/* ... omitted ... */var t = new Timer(10);
t.start();

In the start() method, there are two this keywords. It looks like both this keywords are referring to the same Timer object, but they have different meaning.

When you call t.start(), the value of this becomes t. And the start() function will call setInterval() with doSomething (1st argument) and 1000 millisecond (2nd argument). And the return value, intervalId, will be stored in this._interval or t._interval.

One second later, when the doSomething() is called, the function is not bound to the t object anymore. You can ask the 3 questions to determine the value of this. (1) Is it called with new keyword? No. (2) Is the function explicitly bound to an object? No. (3) Is it called within a containing object? No. Answer to all these 3 questions are No. So the value of this becomes global (or undefined). And the global doesn’t have _process() method. That’s why we see the error message.

Solution to the problem

How can we solve the problem with this being dynamic? One of the solution is to create a function that binds this to t object. You can use .bind() function for that.

start() {
var boundFunction = function doSomething(){
this._process();
}.bind(this);
this._interval = setInterval(boundFunction, 1000);
}

The boundFunction is a new function created based on doSomething() with .bind(this). Within the boundFunction, the value of this will always be the same this as in start() function. So when you call

t.start();

The value of this is t inside of the start() function, and you force this inside boundFunction to be t. So whenever boundFunction is called, this will always refer to t.

Conclusion (TL;DR;)

JavaScript has different meaning of this. In classical Object Orient language like Java or C++, this means object itself. However, in JavaScript, this has closer meaning to call site (where the function was called from).

You can determine the meaning of this by asking these 3 questions:

  1. Is the function called with new keyword? Then this is newly created empty object
  2. Is the function called with explicit binding? Then this is thisArg argument from .call(thisArg), .apply(thisArg), or .bind(thisArg)
  3. Is the function called within containing object? Then this is the containing object.
  4. If all the answers are No, this is the default value: global or undefined in strict mode.

And the corrected version of the Timer code is this:

class Timer{
constructor(second){
this._second = second;
document.getElementById(‘timer’).innerHTML = second;
}
_process(){
this._second -= 1;
document.getElementById(‘timer’).innerHTML = this._second;
if (this._second <= 0){
this.stop();
}
}
start() {
this._interval = setInterval(function doSomething(){
this._process();
}.bind(this), 1000);
}
stop() {
clearInterval(this._interval);
}
}
var t1 = new Timer(10);
t1.start();

--

--