Two of my favourite additions to ECMAScript 2015 were the const
and let
keywords. Sadly, I see them being misused quite often on many public repositories and even in some projects I have worked on.
Const (constants)
If Javascript is not the first language you have worked with, then you will be familiar with constants in other languages like Java or PHP. A constant is an immutable variable. An immutable variable means its value will always be the same, it never changes (hence why they’re called constants). Also worthy of note is like let
block scoped variables, constants are block scoped as well.
But developers are using constants for the wrong reasons. Just because a constant is a read only reference to a value does not mean the value inside of it is immutable. Constants only protect you from reassigning the value, not mutating the value within, such as; Map, Array or Object.
If you come across const myConfig = {myProp: 'somevalue'}
one might assume that this would never be allowed to change, but that is not how Javascript works. You can’t assign a new object or change the value, but you can change the properties of the object in the constant. This is because the value inside of the constant is not immutable.
This will work:
const myConfig = {
someProp: 'myvalue'
};
myConfig.someProp = 'fkjsdklfjslkd';
myConfig.aNewProp = 'I did not exist when initially defined';
This will not work:
const myConfig = {
someProp: 'myvalue'
};
myConfig = {}; // Cannot reassign a constant value
However, you can make an object immutable using Object.freeze
which will make mutating the object inside of our constant impossible. However, if your object contains objects of its own, those will not be frozen, so you will need to use Object.freeze
to make those immutable as well.
If we take that first example and use Object.freeze
we get our desired outcome of making the object immutable as well.
const myConfig = {
someProp: 'myvalue'
};
Object.freeze(myConfig);
// Original value remains intact
myConfig.someProp = 'fkjsdklfjslkd';
// New property will not be added
myConfig.aNewProp = 'I did not exist when initially defined';
console.log(Object.isFrozen(myConfig)); // true
My personal use of constants is for avoiding the use of magic literals in my code. See below for an example where I am wanting to see if a user has pressed the escape key on their keyboard.
I know that the escape key text is always going to be “Escape” in supported browsers or browsers still using “keyCode” it will always be 27. These values will always be the same, so I define them and use those when comparing. It makes my code way more readable.
const ESC_KEY = 'Escape';
const ESC_KEY_CODE = 27;
document.addEventListener('keydown', evt => {
let isEscape = false;
if ('key' in evt) {
isEscape = evt.key === ESC_KEY;
} else {
isEscape = evt.keyCode === ESC_KEY_CODE;
}
if (isEscape) {
window.alert('Escape key pressed');
}
}
});
The bottom line with constants is to be careful that you don’t fall into a false sense of security. They don’t necessarily protect your value from being mutated, especially if used with objects, arrays and so on.
If you need to use an object with constant for whatever reason, make sure you freeze it (and any child objects) to make it immutable.
Let (block scoped variables)
By default standard var
variables in Javascript are function scoped. In comparison to other languages like Java, variables are block scoped.
Function scoped variables have been causing headaches for us front-end developers since the dawn of time because they essentially allow you to clobber any existing values or spam your functions with additional variables.
My favourite thing to use let
for is loops. A standard for..loop you might be writing might look something like this:
var i = 100; // This is a bit contrived
var array = [1, 2, 3];
for (var i = 0, len = array.length; i < len; i++) {
console.log(i);
console.log('Loop: ', array[i]);
}
console.log(i); // This will print 3
Let’s clear a couple of things up first. I don’t define an index variable like that, but imagine you were using a variable with the name increment
instead and it was defined at the top of your function and used by other parts of your code.
Then you come along and write a for..loop, you don’t realise you already have a increment
variable and you end up overwriting the existing variable. All of a sudden your original value is gone.
Enter block scoped variables
var i = 100;
var array = [1, 2, 3];
for (let i = 0, len = array.length; i < len; i++) {
console.log(i);
console.log('Loop: ', array[i]);
}
console.log(i); // This will print 100
You already have a variable called i
define and hoisted to the top of your function. Then you define a loop, use let
with the same name and the original value remains intact. Why? Because block scoped variables constrain themselves to the nearest open/closing curly’s {}
therefore this i
variable is only accessible inside of the loop.
Another example is declaring variables inside of if statements:
let myNumber = 500;
if (myNumber === 500) {
let myNumber = 1000;
console.log(myNumber); // Now 1000
}
console.log(myNumber); // Keepin' it 500
I generally try and use block scoped variables as much as I can, it saves me a lot of problems. Scoping variables to blocks also makes the intent of your code a whole lot more clearer and easier to read code as well.
Conclusion
Both const
and let
are blocked scoped. You should be using const
for immutable values; text labels, numbers and anything that never needs to change. You should be using let
to ensure that you are limiting the scope of specific variables where they are being used and avoiding the use of global variables or function scoped variables almost entirely.
even if an object is const without being frozen, it communicates to developers reading the code (just not the machine executing it) what the intention was, which is how we’ve used it – we’re aware that the values are mutable, but going to the extra effort of freezing it (perhaps recursively) wasn’t worth the effort for us.
I know that some folks like using the “const” keyword to make JavaScript work in a more “functional” programming way. It’s definitely good if they understand, as you mentioned, that these can be modified if they are objects.
As for the “let” keyword, I only hope it doesn’t make JS developers feel like they now have a license to write code with high levels of cyclomatic complexity. In my personal opinion, having lots of nested blocks of code in a function generally indicates that they are not keeping things simple enough. It’s good that the “let” function helps protect them a bit from the pitfalls of this practice, but if they think it means that they don’t have to worry about it any more, then I’m not sure how much it’s helping.
Coming from a C++/C# background, and now using TypeScript rather than plain-vanilla JS, our strategy is simple. We *never* use var. Ever. It’s effectively banned from our coding standards.
The let keyword gives us semantics we are more used to. That’s not to say this approach is for everyone, but when I read about variable hosting in JavaScript it was definitely a “what the!” moment and I thought “bugger that!”, I’m not going there.