Saturday, March 28, 2015

Understanding Javascript's this keyword

Funny thing happened the other day. I'm talking to a developer who is designing a project using a MEAN stack, and we're discussing some best practices in JS. We're on the subject of Angular controllers when he says something to the effect of, "I don't really get why we always have to alias this as var self in out controllers. Isn't this always just a reference to that current class object?".
Uh-oh. This developer is a good friend of mine, and a very smart guy and an even sharper software engineer, but this underscores just how few people understand just what the keyword this actually means in Javascript. To be fair, it can have like four different bindings, so let's jump into how this actually works in Javascript:

Quick disclaimer, if you want to really understand the keyword this in JS, look no further than Kyle Simpson's excellent book here.

Ok, onto this. But first, there was something even more subtle in my fellow developer's confusion. Internally, Javascript doesn't have any notion of a class, at all. And it's dynamic, not static, so that whole thought pattern of  "In Java or C++, this is a reference to the object itself bound at compile time" isn't going to serve you well.

Basically, in Javascript this is bound at runtime, and can point to one of four things, depending on how it is called:

1. if the new keyword is used, this points to the newly constructed object. As in:

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

var bob = new Person("bob");
bob.name; //bob

2. If there is any sort of explicit or hard-binding going on as evidenced by the use of call, apply, or bind, then this will point to the specified object. For example (taken from Kyle Simpson's book):

function foo(){
 console.log(this.a); 
}

var obj = {
  a:2 
};

var bar = function(){
 foo.call(obj);
};

bar(); //2
setTimeout(bar,100); //2

bar.call(window); //2

With call(), bind(), etc the first parameter is basically the object to which this bound. So when bar calls foo() via call() and passes it obj, it is forcibly binding its internal call to foo with obj. This cannot be overwritten at a later time, as we see when we invoke bar.call(window) in the last line. In this case, we say that the binding of obj to the this reference in foo() is both explicit and strong, hence, hard-binding.

3. If there is an object that makes or "owns" the call, it is said to set the context for that object. In this case, this will look to that object for binding. For example:
var obj = {
a:2,
f:foo    //hoisting, if you are curios why this works
};

var obj2 = {
 a:4,
 f:foo
};

function foo(){
  console.log(this.a);
}

obj.foo(); //2
obj2.foo(); //4

4. The last case is when there is no other way to provide context to the object. In this case, the global scope is used. Unless we are using strict mode, in which case it is undefined.

function foo(){
  console.log(this.a);
}

var a = 5;

foo(); // 5. Unless 'use strict'; at which point undefined

That's about it, just follow this checklist when you need to resolve a this binding issue. It's worth the time to study how binding can be forced, as in example 2, which will probably be the subject of a future blog post. Until then, happy Javascripting!




No comments:

Post a Comment