JavaScript Mini Lesson #1 - What is 'this' ?

This (ha!) is the first in a series of mini lessons on JavaScript. These lessons are meant to be brief and precise. They will usually explore fundamental concepts that all JavaScript developers need to know.

Understanding this

this in the context of a function is determined by how the function is called.

You see, unlike in some other languages, functions in JavaScript can be switched between objects (functions are first-class). So if your function contains a reference to this, what this refers to is determined when the function is invoked, as opposed when it was written. This concept is referred to as late-binding because this is determined at the last minute possible, so to speak.

obj = {  
  a: 'foo',
  b: function() { return this.a }
}

obj.b();  
// "foo"

obj2 = { b: obj.b }  
obj2.b();  
// undefined

There is an exception to this rule with ES6 arrow functions which impose lexical scope for the binding of this. I will cover this at the end.

Four rules you need to remember by heart

So, when you encounter a this.whatever is undefined error, remember four ways of how this is bound.

1. Default binding

When a function is invoked plainly, this is undefined in strict mode, otherwise it is the global object (e.g. window in the browser).

name = 'Peter'; // name is global

function speak(greeting) {  
   return `${greeting} ${this.name}`;
}

speak('My name is'); // calling speak() straight-up  
// My name is Peter

2. Implicit binding

This is when you invoke a function with a dot operator. It is the most common way. this refers to the object before the dot.

person = { name: 'Peter', speak };  
// I am using ES6 shorthand to assign
// the global speak to person.speak 

// recall that function declarations are hoisted!
function speak(greeting) {  
   return `${greeting} ${this.name}`;
}

person.speak('My name is');  
// "My name is Peter'

3. Explicit binding

Invoking a function with call(), apply(), or bind() allows you to set this explicitly. All of the calls below will return My name is Peter.

function speak(greeting) {  
   return `${greeting} ${this.name}`;
}

const person = { name: 'Peter' };

speak.call(person, 'My name is');  
speak.apply(person, ['My name is']);  
speak.bind(person)('My name is');  

4. Constructor binding

When a function is executed as a constructor, this refers to the function object itself.

const me = new Person('peter');

function Person(name) {  
  this.name = name;
}

It's all about the call site

Recall the statement above about how functions can be passed around in JavaScript? The consequence of this fact is that you need to know the exact function call site in order to determine what this is. The call site simply is where the function is invoked, as evidence by the () after its identifier. This may sound simple, but it can get confusing pretty quickly, especially with callbacks. Examine the snippets below to learn some common pitfalls.

function foo() {  
  return this.a;
}

bar = { a: 'bar', foo }  
bar.foo() // <= this is the call site for foo  
// 'bar'

Also, remember that the body of a functions is only evaluated at run time.

function foo() {  
  this.a = 'foo'
  return this.a;
}

bar = { a: 'bar', foo }  
bar.foo() // <= call site  
// 'foo' is returned because this.a is re-defined in foo

Nested functions do not inherit the this scope:

a = {  
  b: 'hi', 
  c: function(){ 
    console.log(this.b);

    function bar(){ 
      console.log(this.b)
    }; 

    bar(); 
  }
}

a.c();  
// 'hi'
// undefined

Callbacks can be tricky:

bar = {  
  a: 'hi',
  foo: function foo() {
    console.log(this.a);
  }
}

setTimeout(bar.foo, 1000);  
// undefined

The reason we get undefined logged is that the call site is actually within the setTimeout() function. We are passing it a reference to foo, bar.foo. Inside the setTimeout function, foo is just being invoked as foo(). This means that the default binding will apply. Since there is no a defined globally, it is undefined.

To fix this, you would do something like this:

setTimeout(bar.foo.bind(bar), 1000);  

Calling foo.bind(bar) returns a copy of foo that is bound bar.

Quiz Time

Take a look at the following constructor function.

g = new Greeting('Peter');

function Greeting(name) {  
  this.name = name;

  this.foo = function() {
    console.log('Hi', this.name);
  } 

  this.bar = function() {
    this.foo();
  } 

  this.baz = function() {
    Promise.resolve().then(function() { this.foo() });
  } 

  this.qux = function() {
    Promise.resolve().then(this.foo);
  } 
}

Without pasting this code into the console, see if you can guess what the following calls will print out:

g.foo();  
g.bar();  
g.baz();  
g.qux();  

Answers

g.foo(); // Hi Peter  

This one is easy. this == g.

g.bar(); // Hi Peter  

The call inside bar is effectively g.foo().

g.baz(); // TypeError: this.foo is not a function  

We are passing in an anonymous function as a callback to then(). When it comes time to execute this callback inside of then, it will be invoked plainly, which means the default binding applies: foo is undefined in the global scope.

g.qux(); // Hi  

Here were passing into then a reference to foo. It will be called, but since it will be invoked plainly, name will be undefined.


References:
1. Crockford, Douglas. JavaScript: The Good Parts. O'Reilly, 2008.
2.Simpson, Kyle. You Don't Know JS: this & Object Prototypes
3. MDN. Arrow Functions