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
ES2015 const
does not indicate that a value is ‘constant’ or immutable. A const
value can definitely change. The following is perfectly valid ES2015 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++;
ES2015 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’s values 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 object values fully immutable.
Still, Object.freeze()
only works on property-value pairs. There is currently no way to make other objects such as Date
s, Map
s, or Set
s fully immutable.
There is a proposal to add immutable data structures to a future version of ECMAScript.
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 ES2015 code:
- use
const
by default - only use
let
if rebinding is needed - (
var
shouldn’t be used in ES2015)
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 strongly prefer declaring the variable outside of the
if
block — it’s much more readable that way, IMHO. But hey, to each their 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
andlet
, avar
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?Michael J. Ryan wrote on :
Personally, I still use
var
a lot out of habit, I understand how it works, and how the declaration and scoping works… That said, I still think there is value invar
, as a global/leaky variable, such as variables where the binding will change that are scoped at the module level, even if they are exposed via say…In this case it’s intentionally variant, leaky and loosely bound scope. I tend to mostly use
let
when I intentionally want a closed scope,var
the rest of the time, and even then, most of the time I want to use scoping, I get that throughArray.prototype.forEach
/map
/reduce
anyway.Mathias wrote on :
Michael: It’s still unclear why you would use
var
in that example — you could just uselet
. Block-scoping does not prevent the variable from being used in an inner scope!Ruslan wrote on :
To me, current
const
state is like we had with CSS IDs 10 years ago. There were lots of “CSS classes or IDs” articles, but at the end everybody started to use IDs only for JS binding or writing tests.The same is now happening with
const
— it’s just easier to uselet
everywhere and not think about whether the variable can be rebound or not.I’ve been also coding in Ruby, Python and PHP, and none of those languages has such mixed variable declaration as some people want to do in ES6.
So I use
const
only for constants + they are always inUPPER_CASE_FORMAT
.Mathias wrote on :
Ruslan:
const
is not (just) for constants, though, as this article explains.Andrea wrote on :
I’m a victim of the “
const
is for constants” misunderstanding. I’ve recently started using ES6 and even more recently realized howconst
really works… It’s confusing at first.Balint Erdi wrote on :
I’ve also followed this “school of thought” but Matt Beale’s recent post made me think a lot about this. I encourage you to give it a read. His points related to this post are made in the “Constantly
const
” section. Cheers!dSebastien wrote on :
Nice article. As far as I’m concerned,
var
clearly does not exist anymore :)Rob C wrote on :
My approach to ES6 thus far has been the same as per your recommendation:
const
by defaultlet
if rebinding is needed.…and so far so good in strict mode!
It appears to be the general recommendation out there:
And yes, I’ve found that I no longer use
var
.Mathias wrote on :
Rob C: It seems Understanding ES6 is mistaken about what
const
does, though:This reasoning is incorrect;
const
andlet
don’t have anything to do with whether the value of the variable changes.Joseph N. Musser II wrote on :
I’m surprised that people find this unintuitive. It would be unhelpful and unwieldy if
const obj
could somehow affect whetherobj.value
wasconst
.This maps quite 1:1 to the
readonly
concept in C# and other languages.Andre Behrens wrote on :
I would say if we don’t want to fool people into thinking
const
is for constants, thenconst
was a poor syntactic choice.On our team, we treat it as a constant by convention, and everyone knows what that means. Kind of like
_quasiPrivate
variables. For us,const
means “I intend this to be constant”.It would be nice for the language not to have features that require this much explanation.
Jonathan Cutrell wrote on :
I think probably the most compelling use case for
const
is going to be magic numbers.Though it is confusing.
I think, ultimately, using
const
is going to be a mental trigger for future devs of that code — “hey, don’t re-assign this”. But that also requires that they know a particular thing is aconst
, which most people signify with caps.Perhaps you could use
const
for things likewindow
as well, so that it can only be modified, not reassigned tonull
or something. This may help avoid some negative scenarios.Ville wrote on :
Seems several people in the comments don’t understand what
const
means even after reading the article.Shelley Powers wrote on :
The use of
var
within a function anywhere is perfectly fine. Yes, it is. Until the minds behind ECMAScript decide to jump into the shark tank and deprecatevar
, it is a functioning part of the language. The newlet
andconst
are additions to the language, not replacements. Not unless, again, they want to come out and say, “var
is going away”.The use of
var
outside of a function in client-side (browser-based) JavaScript clutters the global namespace and should be discouraged… when we’re assured that applications usinglet
will continue to work for the majority of browser users.If we want block-level scoping than of course use
let
orconst
. If the browser allows it.However, the use of
var
andlet
in Node has different effects. Point of fact, the use oflet
requires strict mode which can have other effects on the application. Some would say this is a good thing, but the effects could lead to unexpected results.No one has been able to definitively say that the use of
let
orconst
improves program performance, or even increases the reliability of the application. So saying “never usevar
” or “always useconst
” is an opinion, not a statement based on imperative testing, or even strong anecdotal observation.As for
const
in ES6, I would suggest it was not cleanly defined.David Walsh wrote on :
Shelley: Please have a read (or re-read) of JavaScript: The Good Parts. Just because you can use some feature of the language doesn’t mean you should. As to your other points:
var
.let
andconst
weren’t introduced for performance reasons. Their use isn’t advocated for performance reasons.Will wrote on :
Personally, I try to avoid all mutable state, even locally in functions.
Object.freeze
orImmutableJS
are invaluable for objects as this article points out, but for other primitives,const
seems very useful. In ES5 I gave up ever needing theelse
keyword. In ES6 I’ll be able to give upvar
andlet
will be a complete non-starter.Ondrej Hanslik wrote on :
Gijs: Actually, anytime you think you need to use
let
just for a complex value initialization, you are doing it wrong. You don’t have to use a ternary operator, you can simply create a function to return you the value. JavaScript is all about functions and closures.let
should be used sparingly andvar
never.var
is staying only and only because of backward compatibility. In most programs almost all the local variables should beconst
.David Wilhelm wrote on :
First, I do understand the behaviour of
const
. But it seems to me that the ‘best practice’ of always usingconst
is problematic. Surely, the value of theconst
keyword is also to communicate the developer’s intention that the value should not be changed. If we always writeconst
instead oflet
, how are other developers meant to know whether they should write code that changes/reassigns to the variable, by changing it tolet
? If we uselet
by default, and only useconst
for values which are never meant to change, then it is clear.To me it seems weird that an object literal assigned to
const
isn’t deep-frozen by default, I expect many will be tripped up by that. Great, we just added to the list of JS “gotchas”. This seems like a good reason to avoid usingconst
for objects, just to avoid the confusion. After all, WTF is the point of aconst
object that can be mutated anyways?Ville wrote on :
const
communicates exactly the developer intent that he won’t be rebinding the variable within that scope. It doesn’t communicate that the developer will never mutate the referenced variable, because that’s how it has been specified. Any other intent would be a misunderstanding of the language.let
communicates that you will be rebinding the variable later in the scope.var
communicates that you are not yet familiar with ES6 ;)David Wilhelm wrote on :
Yes, but the thing is that code changes over time… At first there may be no re-assignment to the
const
. But later, another developer makes changes to the code to introduce a reassignment. So he’s like “I need to change the value of thisconst
, so I’ll just change it to alet
”. But this may be wrong, and introduce bugs. So this is why I think, better to just uselet
, to indicate a variable can be changed/reassigned (the same thing for plain values), and useconst
to indicate “this should never change” so the other developers don’t just change it tolet
when they want to change it.John wrote on :
I don’t care about rebindability!
I have many use cases for having immutable variables, and not many use cases for having an object that I cannot rebind but can change his value.
So for all primitives
const
= immutable, so I use that to express immutability (with caps as convention) and for objects, I just avoid usingconst
, since I don’t care about rebindability, and have no easy option for immutability,const
will make it confusing.Oh, and
'const'.length == 5
, whilelet
is significantly shorter :-)redoPop wrote on :
I agree, David Wilhelm. The adage that “if everything is important, nothing is” applies here: using
const
by default for anything that isn’t rebound diminishes its usefulness as a flag for things that shouldn’t be rebound.Something Mathias said in his post bears thinking about more deeply:
Using
const
by default makes it harder to differentiate between circumstances where this guarantee is actually important vs. ones where the developer has usedconst
just because that’s their go-to declaration keyword.Ville wrote on :
const
is not the important keyword if it’s the default (almost all variables in normal JS code will beconst
).let
is the one that means “pay special attention to this — I will rebind it”.Neither keyword creates bugs as the transpiler will error out on misuse.
David Walsh wrote on :
I really like the idea of
const
by default. It’s a practice I’ve adopted in my ES6 code.const
just means the variable (sic) can only be assigned once. Once you realise this, usingconst
by default isn’t such a big hang up. In fact, I find you seldom need to uselet
.About the only uses I can think of for
let
are (a) loop variables or (b) conditional assignments too big for a ternary. But (a) can generally be replaced with array methods likeforEach
ormap
; whilst (b) is often better refactored into another smaller function (with multiplereturn
s instead of multiple assignments).There’s no use pretending
const
is for immutability andlet
is for mutability. For one thing, once you’re aware of the benefits of immutability you should be using it all the time (if possible) anyway. And if you want to enforce it, useObject.freeze
or Immutable.js.James Gardner wrote on :
I’ll echo any view that we shouldn’t simply blanket-replace
var
withconst
orlet
. The gripe I have with ES6 is that most of the new stuff doesn’t solve core problems in the language itself and that what’s driving this effort is purely political. An effort to adopt more familiar constructs likeclass
andconst
for the sake of accessibility. I findconst
to be another example of this as the keyword is familiar but misleading. Why not fixvar
or introduce a fully immutable type instead? ES6 seems like a wasted opportunity to me. Meh.Mathias wrote on :
James:
No one said you should (at least not on this page). When rewriting existing code as ES6, you should carefully consider each variable and decide to use either
const
orlet
. It’s not a simple search/replace thing. But for new code, there’s no reason to usevar
anymore.How so? It’s exactly the same behavior as in other programming languages. Only C++ is different. The name
const
is only confusing if you (incorrectly) think of the value rather than the binding as the constant thing. Don’t do that.That’s what TC39 did when they introduced
let
andconst
. The semantics ofvar
itself cannot be changed as that would break backwards compatibility.ThomasR wrote on :
Most of the time reassignment really does't do any harm. Personally, I prefer to use
let
by default. I only useconst
to make absolutely clear that the variable must not be reassigned.For exaple, I'd write
but
Why? In my mind, the "default access permission" in JS has always been read/write.
Consider the analogy of adding a property to an object:
If you want to prevent reassignment of
o.x
, you have to explicitly do so:People hardly ever do that, so why would I use
const
by default.Other than that,
var
is dead.gotofritz wrote on :
The problem I have with
const
is:So we are repeating the
something
test many times (it could be an expensive function)? The obvious thing to do would be:…but that doesn’t work because of block scoping. The only things that work is to use either
var
orlet
:In other words, I start off with
const
and then as my code evolves have to move tolet
because of other constraints. I find this distracting, so I’d rather go withlet
by default and useconst
only for magic numbers.Rodney Reid wrote on :
Shelley:
For a while,
let
was actually slower thanvar
by a couple percentage points. I can’t prove in current Chrome Canary 50 or Firefox Nightly 47 that this is still the case though.Glenjamin wrote on :
Is multiple assignment a problem people have been having that causes bugs?
Especially when using a linter and avoiding
for
loops, the problems ofvar
don’t really come up.For this reason I’m not leaping to adopt
const
andlet
, in the cases where they make intent clearer, I suspect the function being too long is why intent is unclear — and prefer to address that instead.Nijiko Yonskai wrote on :
gotofritz: This problem is solved by better variable naming, and is in fact not an actual issue.
In regards to the other items, looking in other languages that are more powerful or knowing languages that are more powerful allow you to have a better idea of the benefits of defaulting to
const
you realize that the variable will be set in stone,let
throws a flag that it can change.That is your ideal state.
Question to propose: Should
const
runObject.freeze
to ensure the appropriate functionality to its children properties?Second question: Should
Object.freeze
also do adeep freeze
functionality?Third question: Should those be false, should
const
be changed, as it doesn’t do what it implies?Fagner Brack wrote on :
Come on. I don’t know why this post was tagged as “opinion” in JavaScript Weekly. Start variables with reduced visibility and referencing lock is a proven pattern that helps to prevent unintentional mistakes by making the intent clear, which is also understood by the programming language. It has nothing to do with readability arguments.
gotofritz wrote on :
Nijiko: That has nothing to do with variable naming!
Max wrote on :
I completely agree with this article.
Update: Actually, I disagree slightly. Wrote a response: ES2015
const
has something to do with mutation.Mike Schwab wrote on :
I suggest avoiding
foo
andbar
, etc.By using variables that have no resonance, you prevent the brain from understanding the lesson. In particular, I completely missed your point about mutability the first time I read the article because you used
foo
for two completely different things.I feel this way about every code example, but particularly for something so abstract I hope you can understand why I find this tradition to be a bane against comprehension. Thanks!
steviesama wrote on :
Gijs: I think not using
var
in the one example, and/or not leading with the declaration of the variable before theif
block scope wouldn’t be practical for any code that ended up with'use strict'
as it would throw errors.Ash wrote on :
gotofritz: Destructuring assignment can be used for this particular case:
Sander wrote on :
Gijs: Correct me if I’m wrong, but in your second example you make the second
foo
global, by not writingvar
in front of it. So your first example is better.Mathias wrote on :
Sander: That’s incorrect. Because
var
hoists, the code snippet Gijs provided, i.e.…is equivalent to:
let
andconst
avoid hoisting-related confusion.nnnnnn wrote on :
No it can’t. When you say
const x = {}
, the value being assigned is a reference to the object, not the object itself, and that reference cannot be changed.But yes, you are right that it isn’t immediately obvious to everyone that the object itself can be modified, including being frozen later with
Object.freeze(x)
.art wrote on :
A good difference between
let
andvar
(at least) is explained on the chapter 2 of ES6 & Beyond by Kyle Simpson:Try the two loops below — the one with the
let
is shorter.Serge Krul wrote on :
Mathias: Well technically is is immutable value-wise since when you change a property on the referenced object you don’t change the value of the variable, which is the reference to the object.
Ivan Kleshnin wrote on :
Exactly.
const
is like an CSSid
— praised but worthless. I described my preference forlet
here: https://github.com/Paqmind/styleguides#let-vs-const.As
const
doest not reduce the number of bugs (never had bugs because of variable redeclarations, had tons of bugs because of mutability in the past), does not improve readability and is a PITA to write – why bother?Oenonono wrote on :
I genuinely don’t see any reason to drop
var
or wholesale replace it withlet
andconst
. The only epidemic problem withvar
is forgetting to use it, which one could equally do withconst
orlet
. Lexical scoping doesn’t improve readability or reliability outside of a few cases likefor
loops; anything that is confusing about redefining a variable is as or potentially more confusing withlet
, though I imagine a specific developer’s background would have a big impact on that perception.As far as
const
is concerned, what you said here has (unsurprisingly) been widely adopted. But from a semantic perspective, I find it misleading.const
is short for “constant”, is it not? If it’s not a constant value and it’s not a “constant” in the sense it’s usually used in module design, then I’m not gaining anything from it. All I’ve done is introduce a new and essentially useless and perhaps even confusing construct for a coworker to parse. I still don’t see the point.Intent is readability. When you use something like
const
you’re saying something about your intent, but if what a reader would expect that message to be differs from the reality, you done fucked up. Intent failed.Chris wrote on :
Ivan:
const
helps prevent accidental type coercion from happening on data structures or functions. Consider this code:Then, somebody accidentally does this:
Now
position
is this string:You may not even see a runtime error depending on what happens. After the type coercion,
position.x
does not cause an error, it returnsundefined
. So your code is now poisoned and you have no guarantee of how bad it might be or how much information you are going to get from the runtime about it.However, if position was declared as
const
, you would get a type error telling you the line where the mistake is, and the rest of the code would still work since the object cannot be coerced into an unexpected type.const
is great. It helps me sleep at night.