This seems to be a very common misconception that just won’t die. I keep running into it in blog posts, Twitter discussions, and even books. Here’s my attempt at setting things straight.
const
creates an immutable binding
ES6 const
does not indicate that a value is ‘constant’ or immutable. A const
value can definitely change. The following is perfectly valid ES6 code that does not throw an exception:
const foo = {};
foo.bar = 42;
console.log(foo.bar);
// → 42
The only thing that’s immutable here is the binding. const
assigns a value ({}
) to a variable name (foo
), and guarantees that no rebinding will happen. Using an assignment operator or a unary or postfix --
or ++
operator on a const
variable throws a TypeError
exception:
const foo = 27;
// Any of the following uncommented lines throws an exception.
// Assignment operators:
foo = 42;
foo *= 42;
foo /= 42;
foo %= 42;
foo += 42;
foo -= 42;
foo <<= 0b101010;
foo >>= 0b101010;
foo >>>= 0b101010;
foo &= 0b101010;
foo ^= 0b101010;
foo |= 0b101010;
// Unary `--` and `++`:
--foo;
++foo;
// Postfix `--` and `++`:
foo--;
foo++;
ES6 const
has nothing to do with immutability of values.
So, how to make a value immutable?
Primitive values, i.e. numbers, strings, booleans, symbols, null
, or undefined
, are always immutable.
var foo = 27;
foo.bar = 42;
console.log(foo.bar);
// → `undefined`
To make an object immutable, use Object.freeze()
. It has been around since ES5 and is widely available nowadays.
const foo = Object.freeze({
'bar': 27
});
foo.bar = 42; // throws a TypeError exception in strict mode;
// silently fails in sloppy mode
console.log(foo.bar);
// → 27
Note that Object.freeze()
is shallow: object values within a frozen object (i.e. nested objects) can still be mutated. The MDN entry on Object.freeze()
provides an example deepFreeze()
implementation that can be used to make objects fully immutable.
const
vs. let
The only difference between const
and let
is that const
makes the contract that no rebinding will happen.
Everything I wrote here so far are facts. What follows is entirely subjective, but bear with me.
Given the above, const
makes code easier to read: within its scope, a const
variable always refers to the same object. With let
there is no such guarantee. As a result, it makes sense to use let
and const
as follows in your ES6 code:
- use
const
by default - only use
let
if rebinding is needed - (
var
shouldn’t be used in ES6)
Do you agree? Why (not)? I’m especially interested in hearing from developers who prefer let
over const
(i.e. even for variables that are never rebound). If you’re using let
without rebinding, why are you using let
in the first place? Is it because of the “const
is for constants” misunderstanding, or is there another reason? Let me know in the comments!
Comments
Gijs wrote on :
I think it’s a matter of opinion whether this:
…is easier to read / “better” than:
I certainly got used to the latter over the course of the pre-ES6 years, and don’t see the point in the extra line in the first example. But then that’s probably just me. :-)
Mathias wrote on :
Gijs: IMHO the former is much more readable, but in such a situation I tend to use the ternary operator, which avoids the issue altogether:
Anyway, I’m more interested in how people use
let
vs.const
. A lot of developers seem to uselet
by default for some reason, and I wonder if the whole “const
is for constants!” misunderstanding is to blame.Gijs wrote on :
Mathias: Right, I mean, clearly the example is overly simplistic — once ternaries start going over 2-3 lines the
if…else
version is easier to follow. The point being that IMO thevar
hoisting means there is still space forvar
overlet
in certain situations. Usinglet
for globals is also problematic because of the global lexical scope (see also https://esdiscuss.org/topic/global-lexical-tier), so that’s another reason to usevar
in some circumstances.Mathias wrote on :
Gijs: I prefer declaring the variable outside of the
if
block — it’s much more readable that way, IMHO. But hey, to each his own. :)The global
let
problem is interesting, but it shouldn’t impact how developers write their code. Creating global variables has always been a bad practice, except when exporting a library — and with ES6 modules and<script type=module>
(and a transpiler until all browsers support it), that problem’s solved.MaxArt wrote on :
Keep in mind that
Object.freeze
does not “deep-freeze” an object, i.e. object values of a frozen object won’t be frozen as well.Mathias wrote on :
MaxArt: Good point! It’s explained on the MDN page I linked to, but you’re right — I should’ve mentioned this in the write-up explicitly. Fixed.
redoPop wrote on :
Kyle Simpson did a great job making the case against using
const
by default then switching tolet
if needed:(Context, and his complete post, is here.)
So far I agree with his recommendation to use
const
only for variables you're planning to treat as non-reassignable.Patrick Mueller wrote on :
From your second example:
This code does not actually throw an exception:
…but this will:
I’ve seen some folks confused by this… including myself. I’ve finally switched over to the
'use strict'
life-style.Mathias wrote on :
Patrick: That code should throw an exception, even in sloppy mode, as per the spec. Perhaps you are thinking of the legacy (non-standard)
const
behavior that used to be in Firefox and is still in stable Chromium at the moment?D wrote on :
To reproduce it in the Chrome Dev console:
Moritz wrote on :
I totally agree with the
const
andlet
usage, but don’t understand why we shouldn’t usevar
anymore?Why is
const
andvar
not a thing? I was under the impression thatlet
behaves the same asvar
, but is block-scoped and not function-scoped.Mathias wrote on :
Moritz: Why would you still use
var
(and the annoying hoisting behavior that comes with it)? There’s nothing aboutvar
thatconst
/let
can’t do better. If you need function scoping, create theconst
/let
at the top of the function.dino wrote on :
@Moritz
The only feature var has is hoisting and it's a feature most people didn't want to use before. It's confusing and all linters report it.
In the middle of const and let, a var might become something obviously used for hoisting. But it's too early.
Moritz wrote on :
Mathias: I can't think of a reason to use
var
instead oflet
either. I guess it just feels weird to abandon it entirely. I might also feel sorry for it, haha :D But there is also no downside in usingvar
though (if function scoping isn't seen as a downside)? Does code with mixedvar
/let
look messy?