The argument for const
Lately as my team moves towards a mandatory ES6 development cycle, we often go into arguments about which keyword to use to declare a variable. As outlined in my previous blog, ES6 brings 2 new variable declaration keywords to javascript: namely let
and const
. Both are new to ES6. Both are block scoped. Both are in the temporal dead zone while hoisted and waiting to be declared.
The argument then is, when do we use const
over let
? #
This stirs the pot quite a bit since we have eslint installed using a unified linter config (spoiler alert: it’s the de facto standard Airbnb style guide). My position is to use const
over let
when possible. As is the position of many others in the industry.
“use const by default, let only where it is required and var to identify code which needs to be refactored”
Immutability of const #
The idea of course is to in the end achieve immutability in the web. const
inherently gives us some of these properties on primitives. The example below shows how the variable harry
would not be changed at the second statement. (Hint: this will throw an error if in strict mode).
const harry = 'potter';
harry = 'weasley'; // Error in strict mode
console.log(harry); // 'potter'
As we can see, at least for strings and numbers (primitives in js), const
provides a level of immutability by design. But what about the other basic blocks of javascript? - namely, arrays and objects. Arrays and objects when declared as a const
can actually be changed in a certain way. If you look at the example below, an array of hogwartsStudents
can be changed by adding or removing items but it cannot be reassigned to Durmstrang students.
const hogwartsStudents = [];
hogwartsStudents.push('Hermione');
hogwartsStudents; // ['Hermione']
hogwartsStudents = ['Krum', 'Igor']; // Error in strict mode
hogwartsStudents; // ['Hermione']
The reasoning for this behaviour is that a const of hogwartsStudents
is a reference to a block of memory for the array. We can still use that reference but we cannot reassign the reference to a new object or array.
What if we want the object properties and the array values to be immutable? #
There are various ways to do this ranging from Facebook’s immutable.js project to using new ES6 functions such as Object.freeze() and Object.seal.
Object.seal(objectArg) stops properties from being added or deleted but it does not stop one from modifying the object’s properties.
Object.freeze(objectArg) stops properties from being added, deleted, or modified making it totally immutable (well except for properties that are arrays or objects since like in the const scenario, they are merely references).
const spellBook = {
'charms': {
'accio': true
},
'curses': ['tarantallegra'],
'ownedBy': 'Half Blood Prince'
};
Object.freeze(spellBook); // changed in place
spellBook.ownedBy = 'Harry Potter';
// Throws an error in strict mode
// Or fails silently otherwise
console.log(spellBook.ownedBy); // 'Half Blood Prince'
spellBook.charms.portego = true;
// Works since spellBook.charms is not frozen
spellBook.charms;
// { 'accio': true, 'portego': true}
The case for Immutability #
Pros
- Less watchers, Object.observes, or any other mechanism which creates a performance overhead in not only when the application is being used but also in the building, maintenance, and maintaining KISS.
- Data is passed from above rather than being listened for so the code has fewer stages such as when something has changed or initialized.
- You can use equality since we can never have states out of sync.
- Since it is immutable, instead of making a copy of an object we can just pass it by reference which results in memory savings and faster execution speed.
- Makes concurrent programming way safer and cleaner.
- Is usually more thread-safe than mutable objects.
- Easier to test because they are easy to mock.
Cons
- Differs from the one-to-one representation of the real world which is inherently mutable.
- Mutability is the default in behaviour imperative languages.
- Creating a new copy of an object from top down at each change can be costly depending on the use case.
Much of what makes application development difficult is tracking mutation and maintaining state. Developing with immutable data encourages you to think differently about how data flows through your application.