[removed] Event Listeners inside a For-Loop

If you want to create multiple event listeners inside a for-loop to save some time, you will have the problem that all listeners are attached to the same DOM element

The core problem is that we pass variable references to the listeners instead of the values stored in the variables.

To show you what I mean, I did set up four input buttons in good old HTML:
 

<input id="myDOMelement0" type="button" value="myDOMelement0" /> <input id="myDOMelement1" type="button" value="myDOMelement1" /> <input id="myDOMelement2" type="button" value="myDOMelement2" /> <input id="myDOMelement3" type="button" value="myDOMelement3" />

If you click on these they should throw 4 console messages. For that you should have Firebug installed. I highly recommend Firebug.

You could of course use any JavaScript Profiler. For instance the Internet Explorer Debugging Tools or Google's SpeedTracer. It doesn't matter.

Now let me show you the problem if you attach event listeners to above html. To save time we implement the listeners via a for-loop:
 

var myArr = [0,1,2,3];

for (var i = 0; i < myArr.length; i+=1) {

    document.getElementById('myDOMelement' + myArr[i]).onclick = function () {

        if (window.console.firebug !== undefined) {
            console.log('myDOMelement' + myArr[i]);
        }
        else {
            alert('myDOMelement' + myArr[i]);
        }
    };
}

If you click on the buttons of the HTML Page your browser will throw myDOMelement3 for every button. Weird.

This is unexpected because above code should throw myDOMelement1, myDOMelement2 and so forth.

But there is a reason to this strange behaviour: You are passing a reference to the variable instead of the value stored in the variable. The last and actual value of the i variable that gets iterated over equals in fact the integer 3.

Another reason why this happens is because the event listeners get invoked at a later time than at the time of their creation.

Furthermore an inner function which in this example is the event listener has always access to its outer function.

In above example the scope where the for-loop resides in is the outer function. That behaviour is called a closure and in situations like these - it marks the root of the problem.

Module Pattern

var myArr = [0,1,2,3];

for (var i = 0; i < myArr.length; i+=1) {

(function (i) {

    document.getElementById('myDOMelement' myArr[i]).onclick = function () {

        if (window.console.firebug !== undefined) {
            console.log('myDOMelement' myArr[i]);
        }
        else {
            alert('myDOMelement' myArr[i]);
        }
    };
}) (i);

}

In order to outwit the closure behaviour you have to get familiar with the Module Pattern. This is a so called JavaScript OOP Design pattern. That means advanced murry purry and I used it in the above code snippet.
The pattern solves our problem because it invokes the event listener immediately and passes along the actual value stored in the i variable.

So finally it works as expected: If you click on the html buttons they will alert the corresponding values. Try it yourself, it's cool.

Surprisingly I learned about this behaviour in a Google Maps API book which I highly recommend. The author is a cool chap named Svennerberg and you can follow him on Twitter.

If you got any suggestions please comment below. Subscribe to my RSS feed at the top right of this page to receive my blog posts in your mailbox.

comments powered by Disqus