JavaScript Closure Misunderstanding
January 26, 2011 Leave a comment
Let’s list some well-known facts:
- JavaScript blocks don’t have scope.
- Only functions have scope.
- Variable access speed depends on “distance” of scopes (in which scope is defined a variable and from which scope we want to retrieve it).
- The furthest scope is global scope and it is considered as slowest scope (That is window object in browser implementation of JavaScript).
- Closures are used to cache scope locally and make variable access faster (and for much more…).
Classical closure example is:
//without closure window.singeltonObject = {/* properties of singleton object */}; getSingeltonObject = function(){ return singeltonObject; }; //with closure getSingeltonObject = (function(){ var singeltonObject = {/* properties of singleton object */}; return function(){ return singeltonObject; } })();
Closure version looks difficult but it is very powerful (for example you can not modify your singelton object, it is closed in a local scope, while when you have defined it in global scope it is accessible from everywere). Les’t explain it:
//without closure //global scope window.singeltonObject = {}; getSingeltonObject = function(){ //local scope return singeltonObject; //goes out of local scope in //global scope, finds variable and //reads it. } //with closure //global scope getSingeltonObject = (function(){ //local scope1 var singeltonObject = {}; return function(){ //local scope2 //while definition of this function local scope1 //is cached and singeltonObject will be found there, //it will not be searched in global scope //that's why it is faster and useful. return singeltonObject; } })();
What happens when we have three or multiple deep closure? I mean, when we have such kind of code:
/** * @param {Number} personId * @return {Object} * Has method logPersonId that logs parameter */ getSingeltonObject = (function(){ //scope0 var singeltonObject; return function(personId){ //scope1 if(!singeltonObject){ singeltonObject = { logPersonId : function(){ console.log(personId); } } } return singeltonObject; } })();
At first glance everything is OK, but there is a bug:
getSingeltonObject(1).logPersonId(); // 1 will be logged getSingeltonObject(123).logPersonId(); //still 1 will be //logged instead of 123 :) why?
Because the function logPersonId is defined once, it caches the scope1 and at that time the parameter personId equals to 1. During the second call logPersonId is already defined and it has cached scope1 when personId was equal to 1 and it still “remembers” it. That is why in both calls value 1 was logged.
What is solution? scope0. We have to update personId in scope0 by creating a new variable in it (for example newPersonId) and assigning it personId everytime the getSingeltonObject is called. The code will look like this:
getSingeltonObject = (function(){ //scope0 var singeltonObject, newPersonId; return function(personId){ //scope1 newPersonId = personId; if(!singeltonObject){ singeltonObject = { logPersonId : function(){ console.log(newPersonId); //newPersonId //will be found in scope0 and it will //equal to parameter of getSingeltonObject //function every time. } } } return singeltonObject; } })();
This was a little about JavaScript closure and its complexity.