Skip to content

About the author of Daydream Drift

Tomasz Niezgoda (LinkedIn/tomaszniezgoda & GitHub/tniezg) is the author of this blog. It contains original content written with care.

Please link back to this website when referencing any of the materials.

Author:

Let `var` Die

Published

Surprisingly, my style of writing JavaScript code allows most variables to be declared as const. One of the major rules I employ when writing my own code and helping others write theirs, is to create new variables with more precisely defined names instead of trying to reuse existing ones. The computational and memory costs of creating a variable are negligible, so clarity can and should be the focus. And yes, this is not what schools teach you to do with constant modifiers in any language. Consts tend to get special treatment. They're often treated as entrypoints for reconfiguring a program, moved to the top of code and given names in capital letters, so they're easy to discern. In JavaScript at least, I'd argue that consts are far more useful.

But let's start from the most obvious outlier in the group - var (I won't even discuss globals, because you should never use them). var has been around for a long time and you can definitely write great scripts with it. It just doesn't reinforce good behaviors in programmers. I personally know developers and have read multiple discussions about how to use var to keep your code readable and still working. The bottom line is, though, that to make var as safe as possible, it has to be used at the very top of functions. Not code blocks, but functions. It's an important distinction. Otherwise, the following happens:

function do_something() {
  console.log(bar); // undefined
  var bar = 1;
}

This is called Variable Hoisting. var statements are moved to the top of the function that contains them, but value assignments are done in the place they're written. It's therefore easier to accidentally end up using values too soon, because no error will be thrown where such code is present, even though it's clearly useless. This issue becomes even more apparent when using code blocks:

for(var p = 0; p < 10; p++){
	buttonElements[p].addEventListener('click', function(){
		toggleButton(buttonElements[p]);
	});
}

toggleButton will always be called with p = 9, because p is not local to every step in the iteration. It will be hoisted to the top of the function containing for. I've made this mistake on occasion and have seen young programmers do it a bunch of times. It's very easy to miss. Both const and let don't have this issue. They work like constants and variables in most languages - they become available after the line where they're declared or desclared and assigned, so there's no harm in using the modifiers somewhere later in code. They are also local to blocks, so the loop shown earlier will work as expected if let (only let, since const cannot change).

On top of all of this, my suggestion is to use const by default and fall back to let only if const simply can't be used. Using const makes it clearer that the value assigned to it won't change. To clarify, const is not equivalent to immutable state. In Javascript, it works just like in most other programming languages. Objects can be created as constants but their contents can still be changed freely, because the reference to the object is made constant, not the contents of it. Making values constants is not a huge difference compared to making variables, but it can improve legibility a bit more, so I'm all for it.

One extra important thing to one at the end is how function declarations work in JavaScript. The following code works just fine:

p();

function p(){
  console.log(new Error().stack);
}

The declared function as a whole will be hoisted to the top of the code which can be misleading, similarly as when using var, except here p is defined when it's called in the first line. But assigning p to a variable can produce wildly different results.

p();

var w = function p(){
  console.log(new Error().stack);
}

Now p is undefined when called and will still be undefined if w and p in the third line are switched. Changing var to let or const will change nothing and force p to be called below the function declaration. What I suggest doing is the same as with var - avoid using function name(){} alone and use const (preferably) or let. Running console.log(new Error().stack); inside of the second function yields one more insight: error stacks reference p and if it's not there, they use w. This is very important for debugging. Calling the second function will return something similar to:

"Error
    at p (mikohiqela.js:10:40)
    at mikohiqela.js:13:1
    at https://static.jsbin.com/js/prod/runner-4.0.4.min.js:1:13850
    at https://static.jsbin.com/js/prod/runner-4.0.4.min.js:1:10792"

Using this stack message it's easy to figure out where the issue occured. Now let's take a look at a different piece of code with the same function implementation provided as the example:

(function(){
  console.log(new Error().stack);
})();

Running this code will spit out:

"Error
    at mikohiqela.js:10:40
    at mikohiqela.js:11:3
    at https://static.jsbin.com/js/prod/runner-4.0.4.min.js:1:13850
    at https://static.jsbin.com/js/prod/runner-4.0.4.min.js:1:10792"

While it's possible to find out where the stack came from by looking at line numbers, in more complex scripts it can be very difficult to do so. Many libraries tend to return or run anonymous functions built using some builder functions, so that some values can be predeclared for them and they're easier to use. When following this pattern, let's make sure those anonymous functions are given a name somehow, so debugging is easier. To simplify even further, the following is also ok for debugging and will cause p to be mentioned in the stack:

const p = () => {
  console.log(new Error().stack);
}

p();

let, var and const were recently mentioned by Douglas Crockford in his "The Post JavaScript Apocalypse" presentation and he came to similar conclusions about the use of each.