Mathias Bynens

ES6 const is not about immutability

· tagged with JavaScript

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!

About me

Hi there! I’m Mathias, a web standards enthusiast from Belgium. HTML, CSS, JavaScript, Unicode, performance, and security get me excited. If you managed to read this far without falling asleep, you should follow me on Twitter and GitHub.

Comments

Gijs wrote on :

I think it’s a matter of opinion whether this:

let foo;
if (something) {
foo = 5;
} else {
foo = 10;
}

…is easier to read / “better” than:

if (something) {
var foo = 5;
} else {
foo = 10;
}

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. :-)

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:

const foo = something ? 5 : 10;

Anyway, I’m more interested in how people use let vs. const. A lot of developers seem to use let 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 the var hoisting means there is still space for var over let in certain situations. Using let 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 use var in some circumstances.

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.

redoPop wrote on :

Kyle Simpson did a great job making the case against using const by default then switching to let if needed:

The mindset I have to be in to realize that a const should no longer be a const, and to then responsibly analyze the potential risk areas I am introducing by changing const back to let / var — and quite possibly needing to refactor the surrounding code more heavily to reduce those risks! — is way more than just a casual and quick, "replace const with let".

(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:

const foo = 27;
// Any of the following uncommented lines throws an exception.
// Assignment operators:
foo = 42;

…but this will:

'use strict'; // <<<<<<<<<<<<<<---------------------!!!!!!!!!!!!!!!!
const foo = 27;
// Any of the following uncommented lines throws an exception.
// Assignment operators:
foo = 42;

I’ve seen some folks confused by this… including myself. I’ve finally switched over to the 'use strict' life-style.

D wrote on :

To reproduce it in the Chrome Dev console:

bar = function() { 'use strict'; const foo = 1; foo = 2; }()
Uncaught TypeError: Assignment to constant variable.(…)

Moritz wrote on :

I totally agree with the const and let usage, but don’t understand why we shouldn’t use var anymore?

Why is const and var not a thing? I was under the impression that let behaves the same as var, but is block-scoped and not function-scoped.

wrote on :

Moritz: Why would you still use var (and the annoying hoisting behavior that comes with it)? There’s nothing about var that const/let can’t do better. If you need function scoping, create the const/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 of let 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 using var though (if function scoping isn't seen as a downside)? Does code with mixed var/let look messy?

Leave a comment

Comment on “ES6 const is not about immutability”

Your input will be parsed as Markdown.