Always đź‘Ź use đź‘Ź const đź‘Ź
Many of the new features of ES6 are features that have a set of well-defined use cases that the average JavaScript developer may not encounter very often. For someone who is, for instance, writing jQuery to enhance the UI of a Wordpress site, ES6 may remain a largely unknown country for quite some time.
But one new feature introduced in ES6 should be picked up and utilized by every JavaScript developer in principle. (The practice of setting up Babel for production is… a different story.) This feature is the introduction of the let
and const
keywords to declare bindings.
Before ES6, JavaScript effectively had two scopes, and two scopes only: the scope within a function, and the global scope. Additional confusion is created by “variable hoisting” - a JavaScript var
declaration will be “hoisted” to the top of its scope, regardless of where it is actually declared. Let’s take a look at an example of this working.
So - what do you expect to be returned if we pass true
to getValue()
? And if we pass false
? Passing true
will do what you’d likely expect, and return "foo"
. But if we pass false
will it return a ReferenceError
or something else? Think about it for a minute, then keep scrolling.
If we pass false
to getValue()
, it will return undefined
. That is because the variable declaration is hoisted to the top of the function… but not the variable _definition_. You can think of it like this:
Indeed, let’s take an example of some real-world code, from graph-scroll
, developed at Bloomberg:
Look closely, and you’ll notice that windowHeight
, n
, and belowStart
are all declared, but not defined. Although this is a perfectly legal JavaScript, it is worth noting that these declarations are, strictly speaking, unnecessary. They are there to make it clear to make the inner workings of the graphScroll()
constructor more obvious to you, the reader.
Now, let’s say you recreated my contrived example function using let
instead of var
. What happens?
This time, getValue(false)
will return an Uncaught ReferenceError
. Whereas var
hoists the declaration, let
does not. The declaration only exists within the first branch of the if-else
statement.
Now, let’s say you changed the declarations within the graphScroll
constructor above to be let
s instead of var
s. Would that change the behavior of the function?
The answer is… no. When placed at the top of this particular function, let
and var
will work the same way. The scope for both is the entirety of the function - let
has block scope. Block scopes are created either a) inside a function or b) inside a block (effectively, anything between a {
and a }
). Traditionally, languages with a C-based syntax have used block scope as the scoping mechanism. JavaScript, however, is less a C-based language than it is a bastard Scheme that uses Java syntax. This is a historical accident that has bred decades of confusion in the JavaScript community.
So what about const?
Okay, you may be wondering why we haven’t talked about const
yet, even though I began by demanding you đź‘Ź only đź‘Ź use đź‘Ź it. const
is itself confusing, and the confusion transcends JavaScript. The tendency - again, transcending JavaScript - is to assume that a constant is an immutable value. THIS IS WRONG. Let’s look at an example:
You might expect that JavaScript will kick up an error - you’ve mutated the value assigned to the declaration, after all. But this is mistaken.
This is because const
is not intended to freeze the value assigned to a declaration. Rather, const
prevents an identifer from being re-declared. Thus:
That is all that const
does. Its sole purpose is to prevent the sort accidental redeclarations that can occur in JavaScript as a result of its scoping rules. const
also forbids declarations that do not provide a definition. For instance:
But wait a second - the plot thickens. Let’s look at another snippet, this time using let
:
As we can see, let
also prevents re-declaration - but not re-definition. So why use const
? The answer is simple: const
is most restrictive way to declare variables. If const
doesn’t work in your particular use case, try let
. You should only use var
as a last resort. It would perhaps be extreme and certainly controversial to call using var
a code smell, but you should think long and hard before using var
. Let’s look at one final example:
The practical behavior is similar, but notice the difference between the SyntaxError
and TypeError
.
Freeze!
So, let’s say you want to do what you thought const
does - that is, declare a variable that refers to an immutable value. Does ES6 have a feature for that? The answer is… no. Luckily, ES5 introduced this feature years earlier! Let’s say we have an array [1, 2, 3]
, but we don’t want to be able to mutate it. Here’s how we could implement it using the Ojbect.freeze()
, which was introduced to little fanfare in ES5.
Notice, however, that the attempt to mutate the array in line 2 doesn’t return an error, although it also doesn’t successfully mutate the array either.
But Nobody Knows How To Use Const… What Do I Do?
In general, const
is widely misunderstood, even by people who are quite smart and should know better. So what should you do? When you see const
in the wild, should you assume the author of the code knows what it means? Are they trying to prevent an identifier from being redeclared, or are do they think they have created a reference to an immutable value?
There is no “real” or “correct” answer to this question. You should do your best to educate other developers on your team and it is good practice to assume, at least on large open-source projects, that the community tends towards intelligence and proficiency rather than stupidity and amateurishness. Beyond that, it is always best to simply listen to your heart. 💖