JavaScript is not C#

Sharing this because it had me confused for half an hour.

I was just working on the new file upload dialog for Zudio 1.1 (I’m replacing Plupload with a custom-written solution which works better with Azure Blob Storage on the server side). The new code is all AngularJS directives with some Angular-UI Bootstrap, including their spiffing progress directive. Except, when I uploaded multiple files, the only progress bar that moved was the last one in the list, and it jumped all over the place. My immediate suspicion, obviously, was “there’s something wrong with Angular-UI”, but then I remembered similar issues when I first started writing LINQ code in C# 3.0 and got caught by the “modified closure trap”.

If you don’t know what that is, consider this code, which is supposed to activate a whole list of things when a button is clicked:

foreach (var thing in things)
{
    button.Click += (o,e) => thing.Activate();
}

What actually happens here is that the Activate method on the last thing in the list will be called (repeatedly?), and the other things’ Activate methods won’t be called at all. That’s because the “thing” variable gets wrapped in a single closure object which is reused each time round the loop, and it ends up pointing to whatever the last thing was.

This was such a common cause of confusion, head-scratching and keyboard abuse that in C# 5 they changed the specification to say that a separate variable should be created each time around the loop, but before that the way to avoid this problem was to do that manually, like so:

foreach (var thing in things)
{
    var thisThing = thing;
    button.Click += (o,e) => thisThing.Activate();
}

Now, when I write this kind of code, I always assign the loop variable to a separate variable, even in C# 5… and also in JavaScript:

for (var i = 0; i < things.length; i++) {
    var thing = things[i];
    button.addEventListener("click", function() {
        thing.activate();
    });
}

Except it doesn’t work in JavaScript, because in JavaScript the var keyword won’t create a new variable within the same scope; var statements are hoisted to the top of the current scope by the runtime. That code above is semantically equivalent to:

var i, thing;
for (i = 0; i < things.length; i++) {
    thing = things[i];
    button.addEventListener("click", function() {
        thing.activate();
    });
}

In JavaScript, the way I work around this is to refactor the body of the loop into a separate function and pass the variable in; that creates a separate copy of the variable each time round the loop.

function setupActivateOnClick(thing) {
    button.addEventListener("click", function() {
        thing.activate();
    });
}
for (var i = 0; i < things.length; i++) {
    setupActivateOnClick(things[i]);
}

If you’re the kind of person who likes nested code that’s hard to read 6 days after you wrote it, let alone 6 months, you can do this as an Immediately-Invoked Function Expression:

for (var i = 0; i < things.length; i++) {
    (function (thing) {
        button.addEventListener("click", function() {
            thing.activate();
        });
    })(things[i]);
}

which has the same effect, but with the added excitement of knowing that I’ll kill you if you do shit like that in any of my code. It’s not “functional programming”, it’s just bloody horrible*.

* Yes, IIFEs are a perfectly valid construct in JavaScript, and pretty much all code is and should be contained in them at the top level to prevent random declarations from hitting the global context, and it’s how JavaScript does modules, and all that, but that doesn’t mean you have to use them everywhere, you damn hipster.

Comments

  1. This has caught me out before… much nicer option is to do something like this (borrowed from knockout source):

    var arrayForEach = function (array, action) {
    for (var i = 0, j = array.length; i < j; i++)
    action(array[i]);
    },

    arrayForEach(things, function(thing) {
    button.addEventListener("click", function() {
    thing.activate();
    });
    }

    • You have created N event listeners for an array of length N.
      Probably upon an page, start-up. What happens if array length changes after that ?

      Why not just simply doing this ( ECMA Script 262) :

      button.addEventListener(“click”, function() {
      things.forEach( function (e) { e.activate() });
      });

      Working snippet : http://jsfiddle.net/dbjdbj/hNRwf/

      This is “semanticaly equivalent” to your code :)

      Thanks …

      • No, it isn’t.

        Your code loops through the things that are in the array at the time the button is clicked, and calls activate on them.

        My code loops through the things in the array once, at a specific point in program execution, and links their activation to the click of the button.

        If the array changes after this point, the items added to it won’t be affected by button clicks.

        It may seem like an arbitrary distinction, but in the code I was writing at the time, it wasn’t.

  2. A good reason to declare variables at the top of their scope. I got a good grip at these things by coding in coffeesript for a few weeks with live javascript compilation in a splitted view (coffeescript compiler put the variable declaration at the place javascript would do it implicilty).

    @roysvork Yes array.forEach() looks more elegant in that case,

  3. Requirement is : “..If I click a button, I want every element of an array to be used …”
    Why would anybody implement it by iterating the array first and then doing “stunt programming” in order to try and pinpoint all the array elements to be used ? Why “shit like that” In any language ?

    Why not just simply doing this ( ECMA Script 262) :

    button.addEventListener(“click”, function() {
    things.forEach( function (e) { e.activate() });
    });

    Working snippet : http://jsfiddle.net/dbjdbj/hNRwf/

    Thanks …

  4. Use a precompiler. Coffeescript:

    for thing in things then do (thing) -> button.addEventListener(“click”, -> thing.activate())

    I find that incredibly readable, but only because functions are first-class objects in Javascript that are sadly obscured by a cluttering syntax. Coffeescript removes the clutter to reveal that Javascript is secretly a lovely Lisp-1 under the covers.

  5. I found eloquent javascript to be a useful resource when I first started with the language, but I have a hard time falling in love with a language that’s so friggin’ hard to debug.

Trackbacks

  1. […] Тут чувак рассказывает о том что JavaScript это не C# […]

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

Follow

Get every new post delivered to your Inbox.

Join 3,750 other followers

%d bloggers like this: