In part 6 of the series Naked JavaScript, I am going to discuss about the concept of closures in JavaScript. In the previous article, I only briefly mentioned the topic of closures in JavaScript. In this article, we will drill down on the absolute details of this interesting topic. Closures in JavaScropt is a very important concept, and it comes in extremely handy when you are trying to innvoke functions or write classes. Not only is it one of the most important concepts in JavaScript, but it is also mesmerizingly interesting and contributes significantly to the expressive power of this wonderful programming language.
We all know that every programming language has scoping rules for its variables. Javascript takes scoping to a whole new level in the form of closures. The beauty of this concept is most easily understood only by means of examples.
Consider a block of code that is written directly inside the script tag, i.e. in global scope.
Since this block of code was written directly inside the script tag, the varible bingo becomes a global variable. Global variables are variables that are declared on the window object. In this case, since you have not specifed a parent object for bingo, the parent is assumed to be 'window'.
This defaulting logic can cause a huge mess, as we learnt in the previous article, because the size of a program graudally grows with time. And with new features coming in every now and then, you are very likely gonna want to name your variables based upon the real world concepts that they represent. However, declaring variables in the global scope can lead to serious problems because one function may end up reading values set by another function just because they use the same variable name. This is where scoping comes into the picture. However, scoping in JavaScript is a completely different beast. Unlike other programming languages like Java, JavaScript does not support block level scope.
One solution to this problem would be to specify a different parent object for each variable having the same name. For example
//Defined on the global 'window' object var bingo={name:'xyz'}; //Defined in a nested object var myObj = {}; myObj.bingo={name:'xyz'}; console.log(bingo.name); console.log(myObj.bingo.name);
As you see, since we specified the name of the object inside another object, we were able to reuse a variable name without any conflicts. The varables bingo and myObj are global properties. i.e. they have global scope. What this implies is that for your own programs, you can create one single global object and define all your functions as properties of this object. In that way, you can ensure that you clutter the global scope with only a single object. So far so good.
Now lets talk about functions.
When you create a function, you frequently declare new variables inside a function to implement your logic. When these variables are declared, they are declared with a scope that spans the braces that enclose the function. Lets see an example.
function popeye(){ var msg = 'I love olive'; console.log(msg); }; function bluto(){ var msg = 'I love olive too!'; console.log(msg); };
As you see, in our function we were able to reuse the variable 'msg' just because they existed in different scopes. Even this is pretty simple, and of course, works as expected. But wait, there's more.
Enter nested functons.
Unlike Java, javascript lets you create nested functions. The syntax of declaring a nested functions is the same as declaring a normal function. Lets see an example.
function popeye(){ var msg = 'I love olive'; function printMe(){ console.log(msg); } printMe(); };
Even this was easy. We just created a new function inside the popeye function and invoked it. But note that you were able to access the value of the variable 'msg' even thought you did not declare it inside the printMe function. So this tells you that nested functions have access to the variables declared in the scope of the outer function. And this nesting can continue further down.
function slimShady(){ var msg = 'I am slim shady'; function iAmSlimShady(){ function theRealSlimShady(){ console.log(msg); }; theRealSlimShady(); }; iAmSlimShady(); }; slimShady();
Ok, even this is pretty easy to understand. We create a function slim shady, which declares and invokes an inner function iAmSlimShady which again declares and creates an inner function theRealSlimShady. But the story does not end here. Irrespective of the depth of nesting, an inner function is able to access the variable declared in the outer function. This is what a closure is all about. The ability of an inner function, to dynamically access the values of variables declared in the enclosing function even after the enclosing function has completed its execution is called a closure. The concept of closures actually becomes blatantly obvious when inner function references are accessed from outside the context of the method in which they were declared. Consider this example.
function printer(msg){ function printMe(){ console.log(msg); }; return printMe; }; var p = printer('Popeye says - Olive is mine'); var b = printer('Bluto says - Your Olive is mine'); p(); b();
What do you see in your console?? What were you expecting? If you have read the other parts of the series, then you will know that I had mentioed earlier that functions are nothing but objects. So, we can simply return a reference of an inner functon from the outer function and thats exactly what we have done. We returned the printMe function from the printer function. Thereby, in our case, both the functions p() and b() refer to the same function i.e. the inner function printMe.
In the first invocation of the function function printer i.e. p, we passed in different string for popeye. In the second invocation, we passed in a string for bluto. And since both p and b refer to the same function - printMe, it may feel that both p() and b() should print the latest value of 'msg' that was passed to the outer function.
Sadly.. Or maybe gladly, thats not what happens. As it turns out, variables declared in an outer function can be accessed by the innner function even outside of the lifetime of the outer function. So even though, upon invocation of the printer function with the argument for popeye's claim, and code execution is completed for the outer function, the inner function will retain a hidden reference to the state of the variable in the outer function. In our case, the variable in the outer function was nothing but the 'msg' argument, which is nothing but a local variable in the printer function.
This implies that if you can preserve the reference to the inner function, you can artificially increase the life of a local variable that was declared in the outer function.
The game of closures gets a bit trickier when used in conjunction with the 'this' keyword. Lets see how that happens using an example similar to the one that we saw above.
function outer(name){ this.name = name; console.log('Outer name : ' + this.name); function inner(){ console.log("Inner Name : " + this.name); }; inner(); }; outer('Popeye'); outer('Bluto');
Observe closely. When I invoked the outer function without specifyng a context, the 'this' keyword in the outer function references the window object and assigns a value of 'Popeye' to the name property of the window object. Even when the inner function is invoked, no context is specified at invocation time, the value of 'this' is also set to the global window object and we can simply print the name.
Now, lets twist this example a bit.
var holder = {}; holder.outer =function (name){ this.name = name; console.log('Outer name : ' + this.name); function inner(){ console.log("Inner Name : " + this.name); }; inner(); }; holder.outer('Popeye'); holder.outer('Bluto');
The only difference in this case was that instead of defining the outer function as a global function, we declared it as a function on the 'holder' object. This sublte change brought about a drastic change in the output. The inner function does not print anything this time. This is what happens instead.
When you are in the outer function, the 'this' refers to the holder object. And you set a name property on it. Simple. But when you invoke the inner function, since you haven't specified any context, the this inside the inner function refers to the window object. And since you have not set any value for a property called name in the window object, all you get is an undefined value.
Lets see another variation of the above example.
var holder = {}; holder.outer =function (name){ console.log('Outer name : ' + name); function inner(){ console.log("Inner Name : " + name); }; inner(); }; holder.outer('Popeye'); holder.outer('Bluto');
In this case we have removed this 'this' keyword. So both the functions are directly referencing the value of the 'name' argument in the outer function. The inner function is able to access the 'name' variable because of the closure. This can still be made more fun by harnessing the real power of closures.
var holder = {}; holder.outer =function (name){ console.log('Outer name : ' + name); function inner(){ console.log("Inner Name : " + name); }; return inner; }; var p = holder.outer('Popeye'); var b = holder.outer('Bluto'); console.log('Invoking Inner functions'); p(); b();
Take a look at the changes made. First of all, the function returns a function reference of the inner function. So now, both the objects p, and b refer to the same inner function. When you create p using the parameter 'Popeye' the local variable 'name' is set to 'Popeye'. And thats the value that is accessible from the inner function. But observe that when you invoke the function with the parameter 'Bluto' the value of the local variable has CHANGED. This is an important point to note.
Later when you invoke the function p(), you are still able to access the value that was passed when the value of 'name' was 'Popeye'. This is what closures do. When you are in a closure, the inner function is able to 'remember' the old value of the outer function that was in its scope. And whenever the inner function is invoked, it works on the reference to the same outer value. So, if you have multiple references to the same inner function that acccesses the same outer value, you can effectively write code as if the values accesed in the inner function are different for different invocations of the same function, as if they are in completely different scopes.
The only variable for which closures is not effective is the 'this' keyword. Thats because each functions has a different value of the 'this' keyword that solely depends upon the way in which the function was invoked. But what if you want to make use of the 'this' reference of your outer functon inside your inner function? Pretty simple. You wrap it in a closure variable. See example below.
var holder = {}; holder.outer =function (name){ this.name=name; var that = this; console.log('Outer name : ' + name); function inner(){ console.log("Inner Name : " + that.name); }; return inner; }; var p = holder.outer('Popeye'); p();
That was pretty easy. We just created a local variable 'that' and assigned the value of 'this' to that. And thats how the cookie crumbles!
In this article we saw the several different examples of closures. Closures are widely used in almost any piece of code that you read about these days so its important to have a thorough understanding of this concept.
Thats all that I have for this article. I hope you found it useful. There are still a couple of interesting things I intend to cover in the upcoming articles of this series. If you feel lost somewhere, go and check out the previous parts of this series. If not, stay tuned for more!
Signing off
Ryan
No comments:
Post a Comment