JavaScript is a garbage collected language, which means the developer does not need to worry about memory allocations. Instead memory is allocated to objects upon their creation and deallocated when there are no more references to them. As in most garbage collection systems, the mechanism involves a reference counting system. Each object maintains a count of how many objects are referencing it. When the count goes down to zero the object is destroyed and the memory returns to the heap. If two objects reference each other, this is called a circular reference. As a result, each object will have a reference count of 1. If neither is referenced by another object then the garbage collection system should be smart enough to garbage collect both of them.
The IE bug
IE has separate garbage collectors for native JavaScript and for HTML DOM that is reflected as JavaScript objects. And the two collectors don't talk to each other. Therefore, if there is a circular reference between a HTML DOM object and a native object then neither is going to be collected for the duration of the IE browser eventually causing a memory leak.
Closures
The IE bug described above is not specific to closures, but it is easily exposed when using closures. Take this example:
function outerFunction()
{
var myElement = document.getElementById("div_id");
myElement.onclick = function() { this.innerHTML = "I'm a leak"};
}
The inner function has access to the variables of the outer function including myElement. When an inner function has access and uses variables of an outer function this is called a closure. In the above example, the JavaScript object myElement contains a reference to a DOM object. And the DOM element has a reference back to the JavaScript object myElement via the closure through the onclick handler. The resulting circular reference between the JavaScript object and the DOM object causes a memory leak in IE.Work Arounds
Fortunately there are several ways to avoid this. One would be to not use closures:
function outerFunction()
{
var myElement = document.getElementById("div_id");
myElement.onclick = clickHandler;
}
function clickHandler() { this.innerHTML = "I'm no longer a leak";}
Another would be to break the circular reference:function outerFunction()
{
var myElement = document.getElementById("div_id");
myElement.onclick = function() {
myElement.innerHTML = "I'm no longer a leak"};
myElement = null; //breaks the circular reference
}
Why Use Closures?
to inherit the variables and arguments of the outer function and avoid the use of global variables. Passing parameters to a setTimeout function is a good example of where closures can be very useful.
The setTimeout Function
function A()
{
var myTimeout = 1;
setTimeout(myFunction, myTimeout);
}
function myFunction()
{
//do something
}
The first parameter of the setTimeout function is a reference to the function to be called after myTimeout milliseconds has elapsed. If myFunction requires parameters then you can do the following:
function A()
{
var myTimeout = 1;
var parameter = document.getElementById("element");
setTimeout(myFunction, myTimeout, parameter); //Oops does not work on IE!
}
function myFunction(parameter)
{
//do something
}
The above works on most browser, but IE. Hence, the need for a closure. Just a note here that the following is wrong:setTimeout(myFunction(parameter), myTimeout)); //This is wrongThis is wrong because setTimeout takes a reference to the function. And the above code will execute myFunction(parameter) and the returned result will be passed to setTimeout. Unless the returned result is the function you want executed at time out, then this is not what you want to do.
Here's a solution using closures:
function A()
{
var myTimeout = 1;
var parameter = document.getElementById("element");
setTimeout(function(){myFunction(parameter)},myTimeout);
}
function myFunction(parameter)
{
//do something
}
In conclusion, closures are a powerful feature of JavaScript, just make sure you know how to use them.
Great article! Answers a lot questions I had.
ReplyDelete