Tuesday 2 August 2011

Variable closure in Javascript

First off; why should we care about Javascript? Silverlight rocks hard and is vastly superior to Javascript both in expressiveness and in standard library toolbox brevity.

A few years into the future, the above question will hopefully be answered with "what the hell is Javascript"?

Unfortunately, Silverlight adoption has not spread to all devices and platforms. For normal development, that is not a problem. Your customers are very likely using Silverlight ready platforms anyway.

The real world, however, is a cruel mistress. Business decisions - or local law - may force you to make your application available to as wide an audience as possible. In those cases, you need to use Javascript. And, when using Javascript, you need to be very careful what you do. It is a terribly undisciplined language with a weak standard library, inefficient arrays, variable types, no proper object orientation, and really weird variable scope.

This short post will focus on the latter problem: keeping your variables properly scoped.

Take the following example code, which is basically an extension method to the String type. It is a handy utility to count the number of characters in a string. If you implement it in your project, the expression "this".count() will evaluate to 4:

String.prototype.count = function (idxParam) {
  idx = idxParam || 0;
  returnValue = 0;
  if (this[idx])
    returnValue = 1 + this.count(idx + 1);
  return returnValue;
};

While this code works perfectly (try it!), there is one small problem with it. Can you spot it? Look closely. Closer. No? Unless you are a seasoned Javascript developer, the problem is impossible to spot and will likely remain invisible for a long time.

Remember I said Javascript has weird variable scope? In the above code, the variables have not been properly closed off and will leak out of the function and into the global namespace. After executing the above function, the variables "idx" and "returnValue" will be available to all functions everywhere. This may not sound like a real problem, but if some other functions also use variables with those names, they will re-use the above variables instead of receiving their own. Needless to say, this is likely to cause problems down the road. (Especially if you introduce multithreading!)

To check this, try to run the following code:
"Hello world!".count();

if (typeof(idx) !== "undefined")
  alert("Oopsie!");

So, how do we fix this? You need to "close" the variables off, so to speak. By nulling them out, you signal to Javascript that they are no longer needed when you exit the function:

String.prototype.count = function (idxParam) {
  idx = idxParam || 0;
  returnValue = 0;
  if (this[idx])
    returnValue = 1 + this.count(idx + 1);
  return returnValue;
  // close variables
  var idx = null;
  var returnValue = null;
};

// Do a quick check that "idx" is now scoped.
idx = undefined;
"Hello world!".count();
if (typeof(idx) === "undefined")
  alert("It works!");

Simple as that. Note that the var keyword is vital here. Without it, the last two lines would be normal program statements, and consequently would not be executed as they occur after the return statement. The magic var statements, on the other hand, are always executed.

The above hack works everywhere - I've tested in IE, Opera, Mozilla, Chrome, in Windows and on mobile devices. In the world of Javascript, this means it is practically defined behaviour. I recommend you adopt this as a best practice and remember it whenever you happen to step in some Javascript.

10 comments:

  1. Eh, why not just put "var" in front of the variable name at the point of declaration instead?

    Makes for much shorter code.

    Unless you want to make Javascript look worse that it actually is of course.

    ReplyDelete
  2. http://www.reddit.com/r/programming/comments/j7z2y/the_secret_to_scoping_in_javascript_magic_var/c29w2yl

    ReplyDelete
  3. Your explication is completely wrong. It is the 'var' that is fixing it, not the '= null'

    In javascript, declarations are per function, so adding the var at the end is the same as adding at the top.

    ReplyDelete
  4. Wow! I'm surprised and honoured that someone already spotted this blog entry posted this to Reddit, especially since I haven't tried to spread the word myself yet. :)

    Thanks for the tip, Anonymous#3! I've tested putting the "var" on top, and it seems to work equally well everywhere. It also sometimes works putting it in the middle! Javascript, go figure...

    So I guess where you put it is just a matter of personal preference. In my opinion, putting it at the end is a bit tidier since it better signals my intent: ie. de-scoping the variable after use.

    ReplyDelete
  5. I'm not sure how this is considered a hack....when this is how the language is defined...

    ReplyDelete
  6. It's actually not so strange...

    In Javascript, unless you use "var" infront of a variable declaration, it's a global variable that you can use outside the function/scope.

    If you don't use "var" infront of "idx" when you define it, you are using the "idx" variable in the global scope instead.

    It's how the language is defined, and it's how other languages work as well (e.g. "local variablename = 1" in LUA).

    ReplyDelete
  7. Anonymous #3 here again:

    NEVER NEVER NEVER put the var stuff down at the end. That is ugly and almost as wrong as you can get (very un-idiomatic).

    function() { foo = 1; ...snip...; var foo = null; }


    Javascript actually does something called "variable hoisting". Anybody with even a *passing* understanding of javascript will see that initial "foo = 1" and curse your name for polluting the global namespaces. They *might* see the ending "var foo = null", but to the JS engine, it is actually red as:

    function() { var foo = null; /* hoisted */; foo = 1; ...snip...; }

    Doing it the way you are suggesting is totally wrong because you are absolutely *NOT* "de-scoping" or "garbage-collecting" the variable, and instead are just confusing future readers.

    But... your code, your rules... who am I to stop you? :^)

    --Robert

    ReplyDelete
  8. You signal your intent to use a variable in the function scope by declaring it with var, that isn't tidy at all. Because you don't ever descope anything, because everything has scope, it just depends if its the right one.

    Also your vars after your return statement don't get executed because its after your return statement.


    Nice troll!

    ReplyDelete
  9. @Anonymous of 6th September

    They DO indeed get executed after the return statement - that's the magic of "var"! Try removing them and observe changed behaviour.

    ReplyDelete
  10. Yes but everyone else knows that nothing should happen after the return function, so you are making the code even harder to maintain by someone else by using this hole in javascripts logic. (They'd probably go "These null decelerations never happen" and delete it, and then it has global scope again).

    The true magic of var in local function scope is that it has no scope after the function is executed by declaring a variable with var initially, so you don't even have to write null statements after the return, which I think is amazing.

    ReplyDelete