Javascript Closures and Memory Leaks

This is me improving my understanding of closures in Javascript. Closures are a powerful part of the language but can also cause memory leaks, so here I’ll first define what they are, how they can be useful, followed by how they cause memory leaks. (I’m basically abridging with slight rewording what I read here and here so I take no credit for the content…).

What is a closure?

If you think a closure is a function within a function, you’re close but not quite there. Here are two examples of the same code, the first is not a closure, the second is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function notClosure(x){
    var tmp = 3;
    function foo(y){
        alert(x+y+(++tmp));
    }
    foo(10);
}
notClosure(2); // Returns 16, no matter how many times you call it
 
function closureExample(x){
    var tmp = 3;
 
    // This returned function is the actual closure
    return function(y){
        alert(x+y+(++tmp));
    }
}
var foo = closureExample(2);
foo(10) // Returns 16
foo(10) // Returns 17 because as a closure it had access to tmp

So the idea here is the first one didn’t return a function (or at least didn’t have an inner function accessible from outside), and the second one did. What made it a closure is that the inner function closed over a variable from the outer function (in this case the variable tmp). This allowed us to access tmp even though it wasn’t in the immediate scope of the inner function and it wasn’t in the scope of our foo call. Another way of changing the non-closure function above into a closure would be:

1
2
3
4
5
6
7
8
9
10
11
// First example above modified to be a closure
var foo;
function nowaClosure(x){
    var tmp = 3;
    foo = function(y){
        alert(x+y+(++tmp));
    }
    foo(10);
}
notClosure(2); // Returns 16, no matter how many times you call it
foo(10); // Returns 17 because it has access to tmp again

Again in this example we had a pointer to a closure function so we could call it somewhere else and still have access to the variables it closed over. Javascript gives a pointer in closures to the variables in its original scope to make this possible. These pointers are what lead to the issues in memory leaks, which I’ll get to in a second. I

When are closures useful?

Before that I want to show instances where closures are very useful. When writing setTimeout’s often it is helpful to pass a parameter. The problem is that writing something like:

1
2
3
4
5
6
// WRONG!!!
setTimeout(speak("hello"), 1);
 
function speak(word){
    alert(word);
}

doesn’t work because setTimeout wants a reference to a function and since we’re trying to execute the function we’ll get the error that it’s not defined. If instead we use a closure:

1
2
3
4
5
setTimeout(function(){speak("hello");}, 1);
 
function speak(word){
    alert(word);
}

it works great because inside the closure we refer to the function we’re calling (if the anonymous function trips you up this is the same):

1
2
3
4
5
6
7
8
setTimeout(closure, 1);
 
function closure(){
    speak("hello");
}
function speak(word){
    alert(word);
}

How do closures cause memory leaks?

Now, getting to the matter of memory leaks (this is a digest version of a more complete treatment here). Javascript is a garbage collected language, which is why we don’t have to worry about allocating memory and all that jazz. It decides to give memory back to the heap when all references to an object are gone, like the ones we get in closures. However there are cases where there is circular reference (one object points to another that points back to the same) which is where the problems come in. Most systems handle it well, but IE doesn’t because it has two separate garbage collectors, one for native Javascript and the other for HTML DOM. So when native points to DOM, and DOM points back neither is aware of the other pointer and the circular reference slips under the radar. Ergo memory leaks in IE. For example:

1
2
3
4
5
6
7
function leakyFunction(){
    var domElement = document.getElementById("div_id");
    domElement.onclick = function(){
        // Closure over DOM element
        this.innerHTML = "I'm a leak";
    }
}

There are a couple ways to fix this. First the no closure route:

1
2
3
4
5
6
function notsoLeakyFunction(){
    var domElement = document.getElementById("div_id");
    domElement.onclick = clickHandler;
}
 
function clickHandler() { this.innerHTML = "Not leaking anymore"; }

Or just nullify the object after putting on the click event:

1
2
3
4
5
6
7
8
function notsoLeakyFunction(){
    var domElement = document.getElementById("div_id");
    domElement.onclick = function(){
        // Closure over DOM element
        this.innerHTML = "I won't be a leak as soon as the circular ref is nullified below";
    }
    domElement = null;  // Sigh of relief!  Circular avoided
}

Leave a Reply


8 + eight =