Thursday, May 10, 2012

Naked JavaScript - Demystifying The this Keyword and Function Contexts

In part 5 of the series Naked JavaScript, I am going to focus on the the usage of the 'this' keyword in JavaScript. It is extremely important to correctly understand the 'this' keyword it is one of those features of JavaScript where even a small and innocent looking mistake can literally make you pull out your hair if you don't know what you are dealing with.

JavaScript, unlike the name, is not a script like version of Java. In fact, its a completely different beast. And even if it contains keywords and features that 'look' like Java, it does not follow the programming conventions of the the same. I believe that the this keyword is one of those perfect examples of JavaScript where almost everyone new to the language is likely to get confused if they have prior experience of working  with other languages, and therefore make silly mistakes. Some of which can be completely devastating and lead to complicated bugs and sleepless nights.




Lets begin with a simple example example we create an object as follows

var myHappyObject = {
 name : 'MrHappy',
 echo : function (){
  console.log(this.name);
 }
};


Now this seems like a perfectly nice object with a happy little fuction that echoes the value of the 'name' property. But hey! dont forget that 'echo' is nothing but an object that holds a reference to a function. And being a reference, it can be passed to a completely different object. For example, you could do something like this.

var duplicateReference = myHappyObject.echo;


Note that I did not invoke the echo function. I only used a reference to the echo function and assigned it to the variable 'duplicateReference'. After this line, the echo object and the duplicateReference object, both point to the same function that prints the value of the 'this.name' inside the function. But there is a small (or should I say, HUGE) difference! Take a look at the following code.

myHappyObject.echo();
duplicateReference();


Note the output on your console. While the first invocation correctly printed the value 'xyz', the second invocation simply crash landed and printed undefined. At first glance, you might wonder why this is so? Do they not point to the same function? Of course they do. Then why the difference?

In order to understand how the above code works, you need to understand the underlying mechanics of the 'this' keyword and play around with the concept of function contexts.




By default, whenever you invoke a function, a function is invoked in the context of the window object.

function outerFunc(){

 console.log('outer ' + this);

 function innerFunc(){
  console.log('inner ' + this);
 }
 
 innerFunc();
}

outerFunc();



When you invoke the outer function, the first line of console.log prints the value of the window object. That happens because, by default, if you don't specify an object during invocation time, the function call is assumed to be something like window.(). Hence when you are inside the outerFunc, the context is set to the window object, i.e. the value of the 'this' variable is set to the window object. And thats how you get the value of the window in the first line of console.log. 


Similarly, in the second console.log, inside the innerFunc, the value of 'this' is printed as the window object. 

Lets take another example. 


var bingo = {};

bingo.outerFunc=function(){

 console.log('outer ' + this);

 function innerFunc(){
  console.log('inner ' + this);
 }
 
 innerFunc();
}

bingo.outerFunc();


This time we defined the outerFunc as a function on the bingo object. The direct implication of this is that you cannot invoke the function directly. i.e. you cannot directly say outerFunc(). Why? That's because if you do so, the javascript engine will search the window object for the outerFunc() function and since you have defined the function on the bingo object, the search will fail and you get a big slap in the face. However, if you have an itch (in all the wrong places) and still feel like messing with things, you can always invoke it by writing the following code 

window.bingo.outerFunc();


The above example also proves that all objects that seem to be orphaned are not actually orphaned. And that the window object has a strong inclination to play god to all of them. 

But if you notice the console messages, you will also note that even though the outerFunc prints the bingo object as the value of 'this', the innerFunc still prints the window object as the value of 'this'. This implies that context is not inherited. i.e. when functions that are defined inside of a function and invoked inside the function itself, they are oblivious of the value of the 'this' keyword in the outer function. Instead, they get their own copy of the 'this' keyword which depends solely on the way in which the inner function was invoked. 

This odd behavior can have serious implications if not understood properly. 



Lets take a look at our previous example of myHappyObject. Lets redefine the object so that it looks something like this. 

var myHappyObject = {
 name : 'MrHappy',
 rename : function (name){
  this.name = name;
  console.log(this.name);
 }
};


Lets try to do the same things that we did earlier and create another reference to the rename function. 

var duplicateReference = myHappyObject.rename;


So far so good. But there is something very nasty in the piece of code that follows as a consequence of the above assignment. Lets try to invoke both the function references and see what happens. 

myHappyObject.rename('abc');
duplicateReference('garbage');


The code executes smoothly without any errors or warning. It even renames the 'name' property of the myHappyObject to 'abc'. Thats what the first line of code does. But then, what does our second invocation of the duplicateReference do? This function also sets the 'name' property of an object to 'garbage'. But for which object does it set the name property?? I hope that you might have guessed the answer by now. Its our very own window object. What happened over here is something very evil. I say evil because it might not have been your intention to add garbage to the window object but because of the way in which the code was invoked, you accidentally created a property called 'name' on the window object and assigned to it the value 'garbage'. 

What if there was already an existing property called 'name' on the window object and it was being used elsewhere. Our little piece of code would probably lead to a bug that would have been extremely difficult to trace. So remember that whenever you make use of the 'this' keywords, pay careful attention to the manner in which the functions are being invoked. 

Another point to be noted is that although we created a duplicateReference to the rename function, you cannot invoke it using myHappyObject.duplicateReference('abc'). The reason is simple. The myHappyObject does not have any property called duplicateReference. Hence the invocation is invalid. However you can always do something like the following code 

duplicateReference.apply(myHappyObject,['yay'])

//The above code is equivalent to
//myHappyObject.rename('yay');


The code in this case becomes equivalent because when you make use of the apply function, the first parameter behaves as the context of the function and hence is the value of the 'this' keyword inside the function. 



Also there was a case where we talked about the inability of an inner function to access the value of the 'this' keyword present in the outer function. That statement, however, is not entirely true. JavaScript has a extremely beautiful feature called closures that makes so many things possible that it makes you go both wild with madness as well as excitement at the same time. And that's what I am going to talk about in the next part of this series. 

So stay tuned, and stay awake. 
For, the fun....
Has only just begun. ;) 

Signing Off

Ryan

No comments: