The release of ES2015 in 2015 introduced two new keywords, let
and const
, which are important block-scoped variables in JavaScript. Until that point, for close to two decades, a variable was declared in JavaScript by using the var
keyword.
var message = "Hello, world!"
So, whatโs the difference?
The three keywords together provide the same fundamental functionality, but differ in the way that they provide it. var
and let
are reassignable values, while const
has limited mutability.
var
and let
can be reassigned - which is the act of setting a value at one point in time, and then using the same variable reference and giving it a new value.
var fruit = "Apple";
// ... do something
if (foo === 1) {
fruit = "Orange";
}
The same can be achieved with let
:
let fruit = "Apple";
// ... do something
if (foo === 1) {
fruit = "Orange";
}
However, const
does not allow reassignment of the reference, nor does it allow the mutation of a primitive. A primitive is a one of six types; a string, boolean, number, bigint, undefined and symbol, while null
is a special type of object.
All primitives are immutable, meaning that their values cannot be changed, but they can be assigned a new value. This is different to stricter and strongly typed languages, where a primitive can mutable because of the way the language works.
Variable mutation through reassignment:
const foo = "Bar";
foo = "something else";
You can however, mutate an object that declared as a const
:
const foo = [];
foo.push("Apple");
foo.push("Orange");
// Foo is now [ "Apple", "Orange" ]
What you canโt do, is set a new value on a previously declared const
by reassignment:
const foo = [];
const bar = {};
foo = bar;
// Uncaught TypeError: Assignment to constant variable.
Why you shouldnโt use var
let
, var
and const
behave in the same way in scope, other than the limited mutability for const
. It is recommended however, to make it clear as to behaviour and treatment of a given property and its value, that you declare mutable variables as let
and immutable variables as const
.
var
can still be used, but the treatment around var
is far more flexible than let
. For example, using a var
, you can do this:
var x = "1";
var x = "2";
// x will have value of "2"
Doing the same with let
will throw an error, indicating the the variable has already been declared within the scope of execution
var x = "1";
var x = "2";
// Uncaught SyntaxError: Identifier 'x' has already been declared
Whatโs the difference with variable scope?
var
is a tricky one to deal with, because even with block-scoped execution, that is an if
-else
statement, a while
loop, or anything executed between curly braces {}
, var
doesnโt adhere to expected declaration rules, which is that anything declared in a scope, lives in that scope.
For example, this is possible, because when a var
is declared outside of a function scope, it will mutate the parent scope, which in a lot of cases, is the window:
if (true) {
var t = "foo";
}
console.log(window.t);
// Prints "foo" to the console
By using var
inside of a function, the declaration is block limited:
function foo() {
var fooT = "foo";
}
foo();
console.log(window.fooT);
// prints "undefined" to the console
This is where let
and const
come in, because they are correctly block scoped, thereby adhering to the rules of declaration within a function scope, conditional block scope or loop scope.
Importantly, they also donโt modify the parent scope:
let fooBar = "foo";
console.log(window.fooBar);
// prints "undefined" to the console
What about using var
or let
in a for loop?
There are multiple ways to iterate through a collection of objects in JavaScript, with while and for being to primary ways.
Below are several ways to iterate through an array collection using for
// for: i is the index
for (let i = 0; i < collection.length; i++) { collection[i] }
// for...in: x is the index
for (let x in collection) { collection[x] }
// for...of: x is the value
for (let x of collection) { x }
The benefit of using let
in a loop, is that let
is block-scoped to the loop itself. That is to say, that you cannot consume the declaration of let
outside of the block that it is declared in.
// Using let
const collection = [ "apple", "orange" ];
for (let fruit of collection) {
console.log("I like to eat 1 " + fruit);
}
console.log(fruit);
// Uncaught ReferenceError: fruit is not defined
However, when using var
, you can still access the fruit
variable:
// Using let
const collection = [ "apple", "orange" ];
for (var fruit of collection) {
console.log("I like to eat 1 " + fruit);
}
console.log(fruit);
// prints "orange" to the console
There are ways around this, such as dropping the declaration of var
inside block scopes, but this opens up another entire can of worms with readability, consistency and correct block scoping.
The bigger issue at hand, is that even when dropping var
, let
or const
in a loop, the variable is automatically declared as a var
:
const collection = [ "apple", "orange" ];
for (var fruit of collection) {
console.log("I like to eat 1 " + fruit);
}
console.log(fruit);
// prints "orange" to the console
I sort of understood some of those wordsโฆ
To clarify the meaning of some of the words and terms used:
- mutate is to change, or modify a variable or property of an object, eg
let foo = 1; foo = 2;
- assignment is to set a value to a variable eg
let foo = 1;
- iterate is to traverse, or navigate from top-to-bottom (left to right) of an array or collection, starting with the first element and ending in the last element
- collection is a structured object that has n elements, eg 1 or more elements, eg.
const foo = [ "apple", "orange" ];