Javascript closure explained using events

 

The Straw Man :  

I need to know how many times a button has been clicked, and do something on every third click...

Fairly Obvious Solution :

// declare counter outside event handler's scope
var counter = 0;  
var element = document.getElementById("button");

element.onclick = function() {  
    // increment outside counter
    counter++;

    if (counter === 3) {
        // do something every third time
        alert("Third time's the charm!");
        // reset counter
        counter = 0;
    }
};

This will work but it does encroach into the outer scope by adding a variable, whose sole purpose is to keep track of the count.

In some situations this would be preferable as your outer application might need access to this information.  But in this case we are only changing every third click's behavior, so it is preferable to enclose this functionality inside the event handler.

Consider this option :

var element = document.getElementById("button");

element.onclick = (function() {  
    // init the count to 0
    var count = 0;

    return function(e) {
        //count
        count++;

        if (count === 3) {
            // do something every third time
            alert("Third time's the charm!");
            //reset counter
            count = 0;
        }
    };
})();

Notice a few things here

In the above example I am using the Closure behavior of JavaScript. This behavior allows any function to have access to the scope in which it was created, indefinitely. To practically apply this, I immediately invoke a function that returns another function, and because the function I'm returning has access to the internal count variable (because of the closure behavior explained above) this results in a private scope for usage by the resulting function... Not so simple?  Lets dilute it down...

A simplified closure

  
//         ____________Immediately executed (self invoked)___________________
//         |                                                                |
//         |      Scope Retained for use        __Returned as the______     |
//         |     only by returned function     |  value of func       |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();  

All variables outside the returned function are available to the returned function, but are not directly available to the returned function object....

Whenever a function is created it retains access to the scope (context) that it was created in. Because Javascript functions are objects that can be passed around to different contexts, this creates a very powerful dynamic. Where a function can be created in one context, passed to another, and retain access to its original context.

func();  // alerts 'val';  
func.a;  // undefined  

Get it? so in our primary example, the count variable is contianed within the closure and always available to the event handler, so it retains its state from click to click.

There you go, you're now fully encapsulating this behavior

( jQuery considerations )

What happens if I assign the click event to multiple elements at once?

When you have a selector that selects many DOM elements you can bind a click event ot all of them at once

$('.manyElements').click((function() {
    ...
})());

To understand how the above strategy applies to this situation, you need to understand that when this method of attaching event handlers to multiple objects is used, jQuery is attaching a reference of the function to all DOM elements...  This means that if the function object is changed, then it will be reflected across all references to that object.  So in this case it would appear as though all of the elements are sharing the same counter.  This is neat when you understand this because this could be the intended behavior in a lot of cases.  Although, it is very frustrating behavior when it's not intended.

What if I want each element to be selfish and not share their functions?

If you want each item to have their own function (and count), then you need to iterate over the objects attaching the event individually.

$('.manyElements').each(function() {
    // attach one at a time
    $(this).click((function() {
        ...
    })());
});