Ξ bigXi

March 30, 2006

Mystery solved

Filed under: Ajax,IE Memory Leak,JavaScript — bigxi @ 9:46 pm

QuirksMode discussed a mystery about IE memory leaks. Here is the code:

    window.onload = init;    function init()
    {
        createLinks();
        var x = document.getElementsByTagName('a');
        for (var i=0;i<x.length;i++)
        {
            x[i].onclick = function () {
                this.firstChild.nodeValue = ' Clicked! – ';
            }
        }
    }

At first look, this code should leak because there is a circular reference:

  1. Any link has a reference to the anonymous onclick handler.
  2. The anonymous onclick handler forms a closure which contains a reference to the variable x.
  3. x is an array that references all links in the page.

The fact is, however, it doesn't leak. Go test for yourself. Write a createLinks function that appends 10,000 links to the page. Bounce your browser between the page and a blank page as many times as you want. The memory doesn't grow.

Among the various explanations, Laurens van den Oever mentioned something along the lines that, in fact, the variable x is not an array but a NodeList, which upon page unload would empty itself and therefore, there's no longer a circular reference. But still some people aren't convinced. So here's my proof that he is actually right.

This is my modified test:

<html>
<body>
<script language="JavaScript">
    window.onload = init;    function init()
    {
        createLinks();
        var x = document.getElementsByTagName('a');
        for (var i=0;i<x.length;i++)
        {
            x[i].onclick = function () {
                var msg = "";
                for (var i = 0; i < x.length; i++) {
                    msg += x[i].innerHTML + "\n";
                }
                alert("I have references to:\n" + msg);
            }
        }
    }
    function createLinks() {
        for (var i = 0; i < 5; i++) {
            var alink = document.createElement('A');
            alink.href = "#"
            alink.innerHTML = "Link #" + i;
            document.body.appendChild(alink);
        }
    }

    function removeLinksExceptFirstOne() {
        var x = document.getElementsByTagName('a');
        var cntLink = x.length;
        for (var i = 1; i < cntLink; i++) {
            document.body.removeChild(x[1]);
        }
    }
</script>
    <p><button onclick="removeLinksExceptFirstOne();">
    Remove Links Except First One</button></p>
</body>
</html>

The the onclick handler will now report which elements it has references to. When you load the page and click on one of the links, it says it has references to all links in the page. Now click on the button to remove all links except the first one. Click on the link that's left. There's only one reference! So, indeed, when the page unloads, x will be empty. No more circular references.

In the next test I introduce a global array y (a true one) and use that to setup the onclick handlers instead of x:

<html>
<body>
<script language="JavaScript">
    var y = [];    window.onload = init;    function init() {
        createLinks();
        var x = document.getElementsByTagName('a');
        for (var i = 0; i < x.length; i++) {
            y[i] = x[i];
        }
        for (var i=0;i<y.length;i++)
        {
            y[i].onclick = function () {
                var msg = "";
                for (var i = 0; i < y.length; i++) {
                    msg += y[i].innerHTML + "\n";
                }
                alert("I have references to:\n" + msg);
            }
        }
    }

    function createLinks() {
        for (var i = 0; i < 5; i++) {
            var alink = document.createElement('A');
            alink.href = "#"
            alink.innerHTML = "Link #" + i;
            document.body.appendChild(alink);
        }
    }

    function removeLinksExceptFirstOne() {
        var x = document.getElementsByTagName('a');
        var cntLink = x.length;
        for (var i = 1; i < cntLink; i++) {
            document.body.removeChild(x[1]);
        }
    }
</script>
    <p><button onclick="removeLinksExceptFirstOne();">
    Remove Links Except First One</button></p>
</body>
</html>

And sure enough, the onclick handlers still have references to all links after they are removed from the page.

Now change removeLinksExceptionFirstOne to this: 

    function removeLinksExceptFirstOne() {
        var x = document.getElementsByTagName('a');
        var cntLink = x.length;
        for (var i = 1; i < cntLink; i++) {
            document.body.removeChild(x[1]);
        }
        y.splice(0, y.length);
    }

You'll see again that the onclick handlers no longer have references to any links.

It's important to understand that closures don't necessarily mean circular references. You can break circular references to avoid memory leak at any point of program execution. There's no need to wait till the page unloads. Often you'll find that the best place to break any circular references is at the point where the closure is formed.

Advertisements

2 Comments »

  1. free lesbo sex pics

    Comment by hdijfkux — February 6, 2007 @ 11:56 pm | Reply

  2. I bet your coworkers were bouncing off the walls and super hyper after this…at least until their blood sugar crashed. Click https://twitter.com/moooker1

    Comment by barnettbrewer78878 — April 8, 2016 @ 1:12 pm | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: