Mark Brouch

@markbrouch

Why You Shouldn’t Use ‘var’ Anymore

And how ‘let’ solves a common JavaScript “gotcha”…

I’ve been writing JavaScript using the ES2015 (ES6) syntax for awhile now and have grown to appreciate many of the language changes for their elegance and simplicity. One of the first and easiest changes I adapted to was using let/const instead of var. However, I took for granted the benefit let provides over var; it isn’t just a flashy new syntax for var, but rather provides an important scoping mechanism.

I took for granted the benefit let provides over var...

It’s worth noting first that the vast majority of variables I declare are best suited for using const instead of let/var. This is because const will throw an error if an attempt is made to change its value after it has been declared, a useful feature to prevent accidental mutation. However, a mutable variable is often needed, particularly as a counter in looping scenarios. But why should you use let instead of var for these situations, if they both provide the basic mutability required of them?

The simple answer is that let provides block-scoping that is absent in the function-scoped var. This explanation obviously begs a practical example. Allow me to demonstrate this important distinction using a classic front-end engineering interview question:

In this example, what will be printed in the console?

var callbacks = [];
(function() {
for (var i = 0; i < 5; i++) {
callbacks.push( function() { return i; } );
}
})();
console.log(callbacks.map( function(cb) { return cb(); } ));

In this example, we loop 5 times, each time pushing a function into the callback array. After the array is filled with 5 functions, we run each of them, logging their result to the console. A novice engineer might answer (incorrectly) that the result is [0, 1, 2, 3, 4], a reasonable analysis, but one that falls victim to the JavaScript “hoisting” gotcha.

Don’t be a victim. Avoid the hoisting trap with ‘let’!

The correct answer is actually [5, 5, 5, 5, 5], and makes sense when you consider what hoisting does behind the scenes:

var callbacks = [];
(function() {
var i;
for (i = 0; i < 5; i++) {
callbacks.push( function() { return i; } );
}
})();
console.log(callbacks.map( function(cb) { return cb(); } ));

Notice how JavaScript hoists the variable declaration to the top of the function block, causing the value of i to be 5 by the time each of the callback functions have executed, since the for loop has incremented completely before any of these functions are called.

There are a number of ways to classically solve this problem and get the script to log [0, 1, 2, 3, 4] to the console, but let gives us a very simple solution:

var callbacks = [];
(function() {
for (let i = 0; i < 5; i++) {
callbacks.push( function() { return i; } );
}
})();
console.log(callbacks.map( function(cb) { return cb(); } ));

That’s it — just replace var with let and the example works as expected! This is thanks to the block-scoping behavior of let. Rather than being hoisted to the top of the function’s scope, let stays in the block scope of the loop, causing a separate instance of i for each iteration.

So, should you ever use var? As you have likely gathered from the title of this article, I am of the opinion that you should never use var. Technically, var is still useful in situations where you want to maintain function scope instead of block scope, but I am of the firm belief that if you need to rely on an unintuitive hoist to get your script to work, you have bigger problems 😜.

Update: In response to two of the most common points of discussion readers are bringing up:

  1. It is true that const isn’t truly immutable. For instance:
const myNotQuiteImmutableObject = {
thisCanBeChanged: "not immutable"
};
myNotQuiteImmutableObject.thisCanBeChanged = "see I changed it.";

However, this still prevents basic mutation like:

const immutableString = "you can't change me";
immutableString = "D'OH!"; // error

If you need real immutability, check out Facebook’s excellent Immutable library.

2. “let isn’t supported in {mySuperAncientBrowser}.” This is true, and even somewhat recent browsers still don’t have support for let. This has an obvious and easy solution: Use Babel. Babel will allow you to use all the best and latest features of JavaScript and then transpile into a vocabulary that even simple old IE8 can understand (with some exceptions).

More by Mark Brouch

Topics of interest

More Related Stories