This article is going to focus on a piece of the java-script language that I had trouble understanding, and may be a point of confusion for you. Today I’ll be looking at closures and lexical scoping. Last time, I mentioned that Javascript has more in common with functional languages, than classical languages. Today will be no different as closures and lexical scoping are two more features that come from the functional programming ancestry that Javascript has.
What’s a closure?
To understand what closure is you first need to understand stack-frames. In PHP a stack-frame is created each time a function is called.
function sayHello($name) {
$return = 'Hey there'.$name;
return $return;
}
sayHello('Peter') // echos 'Hey there Peter'.
The above function when run will create a stack-frame for itself, and $return will exist and be available inside that stack-frame. Once sayHello() is done $return vanishes into the garbage collector, and is gone until the next time sayHello() is invoked. However, in Javascript we are able to declare inner and anonymous functions. These functions are enclosed by their containing function and are said to form a closure. A simple example of a closure is
function sayHello(name) {
var phrase = 'Hey there' + name;
var sayHi = function() {
alert(phrase);
};
return sayHi;
}
var talk = sayHello('Peter');
talk(); // alerts 'Hey there Peter'
In Javascript functions still create stack-frames however, inner functions have access to any of the containing functions local variables. So a closure is a stack-frame that doesn’t go away after a function is complete. In the above Javascript sayHi is an inner function of sayHello. As we saw when we executed talk() it still had access to the name variable of its containing function. You create a closure in Javascript each time you put a function inside another function. Closures in Javascript preserve all the local variables that existed in the function when it completed. This phenomenon of local variable preservation is referred to as Lexical Scoping.
Ok, so what?
So by creating closures we can do a number of things. First off we can create visibility, something that many people seem to think Javascript lacks. The following example illustrates this.
function Beer(type) {
this.type = type;
var volume = 0.5;
var lessBeer = function() {
volume = volume - 0.5;
};
this.drink = function() {
if (volume === 0) {
console.log('no more beer :(');
return;
}
lessBeer();
console.log('mmm beer.');
};
};
var stout = new Beer('stout');
console.log(stout.type); // prints 'stout'
stout.drink(); // prints mmm beer
stout.drink(); // prints no more beer :(
stout.volume = 10; // trying to top up doesn't work!
stout.drink(); // prints no more beer :(
stout.lessBeer() // TypeError? must be private
If you run the above code in your favorite Javascript console You should get the results in the comments. What this illustrates is that you cannot fill a beer back up, and that by using closures you can create private variables and functions that cannot be accessed from outside the object. jQuery leverages closures extensively.
More on lexical scoping
Lexical scoping is a fancy term that refers to a function remembering and preserving its state between and after executions. However, lexical scoping in Javascript is not truly static, as the wikipedia may lead you to believe. A simple example of lexical scoping is
var count = 5;
function tellCount() {
console.log(count);
};
tellCount(); // prints 5
count = 7;
tellCount(); // prints 7;
This shows how the local variables in a scope are preserved but the values will always reflect the most current values. While count is contained inside the closure created by tellCount() it updates to reflect the current value in the top most scope, which in this case is window. This trick is useful and infuriating at times. If you’ve ever tried defining functions in a loop you’ve probably been dismayed to find out all the functions always reflect the last value in the loop.
So loops and lexical scoping will eventually cause you some grief. The quickest way to achieve this grief is by defining functions in a loop. Lets say you want to loop over an array and make links that say their value, seems easy right? Well until our good friend lexical scope shows up and drinks all the programatic beer. Take the following example.
var numbers = [ 1, 2, 3, 4, 5 ];
for ( var i in numbers) {
var num = numbers[i];
var anchor = document.createElement('A');
anchor.setAttribute('href', '#');
anchor.appendChild(document.createTextNode(num));
document.body.appendChild(anchor);
anchor.addEventListener('click', function(event) {
alert('My number is ' + num);
event.preventDefault();
}, false);
}
If you run this in firebug’s console you’ll get five links, that have click events on them. However, each link will say ‘My number is 5’, all because of lexical scoping. The event listener uses a closure to refer to num however, it doesn’t maintain the value of num from when the function was declared but instead maintains the last know value of num. Which just happens to be 5. Now most javascript libraries give you a good work around by allowing apply events to collections of elements. jQuery has $.each(), and its $.bind() and friends. Mootools has a similar construct in Element.addEvent. However, if you need a loop variable inside an event one way to do it is to do the following:
var numbers = [ 1, 2, 3, 4, 5 ];
for ( var i in numbers) {
var num = numbers[i];
var anchor = document.createElement('A');
anchor.setAttribute('href', '#');
anchor.appendChild(document.createTextNode(num));
document.body.appendChild(anchor);
var clicker = function(number) {
return function(event) {
event.preventDefault();
console.log('My number is ' + number);
};
};
anchor.addEventListener('click', clicker(num), false);
}
Because the scope container in javascript is a function, you need to use additional functions to bind variables to different scopes. In the above example we use the function clicker to make a closure that contains the correct value for num which becomes number in the closure.
So that’s all for today. Lexical scoping and closures can be a tricky subject to grasp but once you do, they are a powerful feature of the language that you will wonder how you lived without.
0 comments:
Post a Comment