For this blog post, you should know how JavaScript’s function-scoped var declarations work. Consult [1] if you don’t.
// 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.
// 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.
// 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 definedInstead 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:
function foo(x, y) { if (x > y) { [y, x] = [x, y]; } ... }