Variable declarations: three rules you can break

[2012-11-13] dev, javascript, jslang, jsstyle
(Ad, please don’t block)
This blog post mentions three rules that are commonly given for using var statements. And then tells you when you can break them. To make things easier to understand, I somewhat disparagingly call the rules “conventional wisdom” and breaking the rules “unconventional wisdom”. But I will also explain why the rules have been created in the first place.

For this blog post, you should know how JavaScript’s function-scoped var declarations work. Consult [1] if you don’t.

Three rules you can break

Rule to break: Don’t put a var statement inside a block

Conventional wisdom in JavaScript tells you that the following code is bad:
    // Unconventional wisdom
    function foo(x, y) {
        if (x > y) {
            var tmp = x;
            x = y;
            y = tmp;
        }
        ...
    }
Point: Looking at the code, one might think that the variable tmp only existed inside the then-block of the if statement. What really happens is that the variable declaration is hoisted – the declaration is moved to the beginning of the function, the assignment stays where it is. Hence, the following version better reflects what’s actually going on:
    // Conventional wisdom
    function foo(x, y) {
        var tmp;
        if (x > y) {
            tmp = x;
            x = y;
            y = tmp;
        }
        ...
    }

Counter-point: From the perspective of JavaScript semantics, the existence of the variable tmp is not limited to the then-block. However, conceptually, tmp is limited to that block: it isn’t used anywhere else and if one removes the block, it should be removed, too. Thus, the unconventional version expresses the author’s intention well. But you have to be careful to only use each pseudo-block-local variable once, because if you declare a variable a second time, it keeps its current value. Your code might assume that it is undefined, afterwards.

    function f(a, b) {
        if (a < 0) {
            var tmp;
            console.log(tmp); // undefined
            tmp = 3;
            ...
        }
        if (b < 0) {
            var tmp;  // declared again 
            console.log(tmp); // 3
            ...
        }
    }
    f(-1, -1);

Conventional wisdom is right when it comes to long functions. Then you run the risk of losing track of variables. However, functions shouldn’t be longer than 5-10 lines, anyway. At that size, the increased conceptual clarity is usually worth the risks.

Rule to break: Don’t put a var statement inside a loop

Conventional wisdom tells you: don’t do the following.
    // Unconventional wisdom
    for (var i = 0; i < 10000; i++) {
        var foo = 1;
    }
Instead, this version is supposed to be faster:
    // Conventional wisdom
    var foo;
    for (var i = 0; i < 10000; i++) {
        foo = 1;
    }
However, conventional wisdom is wrong, in some engines the conventional version is even a tiny bit slower, as demonstrated in this jsPerf test. Performance-wise, it doesn’t matter much which of the two versions you use.

Rule to break: Use a single var statement per function

Conventional wisdom tells you that you should declare all variables at the beginning of a function, with a single statement. For example:
    // Conventional wisdom
    var foo = 1,
        bar = 2,
        baz = 3;
There are two justifications for this rule. First, hoisting never leads to surprising effects with such code. Second, you avoid redundantly repeating the keyword var. We have already seen that other considerations can sometimes take precedence over playing it safe w.r.t. hoisting.

Avoiding the repetition of var has a negative effect: If you forget a comma, you can inadvertently create global variables. For example:

    var foo = 1  // no comma
        bar = 2,
        baz = 3;
Because the tokens 1 and bar are separated by a newline, JavaScript internally inserts a semi-colon [2]. Thus, the last two lines contain normal assignments to bar and baz. In non-strict mode that means that global variables will be created. Let us try the same code in strict mode [3]:
    (function () {
        "use strict";
        var foo = 1
            bar = 2,
            baz = 3;
    }());
Now we are told that we have done something wrong: we have assigned to a variable that doesn’t exist, yet:
    ReferenceError: bar is not defined
Instead of making multiple declarations via a single var statement, I prefer the following approach:
    var foo = 1;
    var bar = 2;
    var baz = 3;
This approach has several advantages:
  • Forgetting punctuation is not an issue. This time, automatic semicolon insertion works for you, not against you.
  • It is easier to rearrange and remove parts.
  • Everything is automatically indented correctly.

ECMAScript 6

ECMAScript 6 will finally bring us block-scoped variable declarations, via let statements. Additionally, it offers destructuring assignment: a pattern on the left-hand side of an assignment operator lets you access the innards of the right-hand side. Destructuring assignment obviates the need for a temporary variable in the function foo (Sect. 1.1) and leads to more succinct code:
    function foo(x, y) {
        if (x > y) {
            [y, x] = [x, y];
        }
        ...
    }

Conclusion

We have looked at three rules about var statements that you can and often should break. As always, don’t follow advice (neither the rules nor this blog post) blindly, make sure you know what you are doing. Hopefully, this post helped you with that endeavor.

References

  1. JavaScript variable scoping and its pitfalls
  2. Automatic semicolon insertion in JavaScript
  3. JavaScript’s strict mode: a summary