Using Closures in Javascript

Robin Ward's recent blog post on Why Discourse uses Ember.js was interesting, as a contribution to the ongoing debate about the Javascript Cambrian Explosion.

However, he leads with an example:

For example, on the bottom of every discourse post there is a button a user can click to like a post. When clicked, it vanishes and adds a footer below the post saying you liked it.

If you implementing this in jQuery, you might add a data-post-id to the post. Then you’d bind a click event on your button element to a function that would make the AJAX call to the server. However, the click function passes a reference to the button, not the post. So you then have to traverse the DOM upwards to find the post the button belongs to and grab the id from there. Once you have it, you can make your XHR request. If the XHR succeeds, you then have to traverse the DOM downward from the post to the footer, and add in the text.

At this point it works, but you’ve tied your implementation of the button click to a particular DOM structure. If you ever want to change your HTML around, you might have to adjust all the jQuery methods that accessed it.

I'd agree about the data-post-id kludge ... it is a horrible wart in jQuery. However, Javascript has a much nicer way of doing these things, using closures:

function show_comment(parent, comment_id, content) {
    var comment_div = $('<div>').addClass('comment').text(content).appendTo(parent);
    var comment_like = $('<button>Like</button>').appendTo(comment_div);
    comment_like.click(function() {
        // Closure #1
        $.ajax('/me_too', {
            data: { comment_id: comment_id },
            success: function () {
                // Closure #2,
                comment_div.addClass("liked");
                comment_like.hide();
            }
        });
    });
}

comment_id, comment_div and comment_like are kept in the lexical scope of the show_comment function, and so are still available when the "Like" button is clicked ... the callback functions called by comment_like.click and by $.ajax can use these variables. If you call show_comment many times, each call will have its own lexical scope with its own values to remember.

There is one major trap: functions create lexical scopes, not blocks. So the following:

function not_going_to_work() {
    for (var i=0; i<10; i++) {
        $('<button>').text("Button "+i).click(
            function () {
                alert("Button " + i + " Clicked");
            }
        ).appendTo(document.body);
    }
}

... isn't going to work. Whichever button you click, it'll say "Button 10 Clicked", because the loop counter i exists in only one scope, that of the not_going_to_work function.

To get around this, we add an anonymous inner function so that each button has its own associated scope:

function is_going_to_work() {
    for (var i=0; i<10; i++) {
        (function (n) {
            $('<button>').text("Button "+n).click(
                function () {
                    alert("Button " + n + " Clicked");
                }
            ).appendTo(document.body);
        })(i);
    }
}

I've written a bunch of HTML5 / javascript stuff in recent years which uses closures as a way of never actually having to traverse the DOM. It is easy, fast, works well within the Chrome debugger and reduces your reliance on jQuery (etc).