With the release of ES6 (or ES2015, whichever you prefer) in 2015, we were given two new ways to define variables: let
and const
. To understand what value these provide, we'll take a look at the original way to define variables with var
and its problems, then we can see how let
and const
overcome these.
var
Since the beginning of JavaScript, var
has been the standard way to declare variables. Let's look at some of the strange behaviors you might have encountered when using var
.
Variable hoisting is an interesting behavior in JavaScript which you've probably run into without even realizing. Take a look at this:
console.log(myName); // this should produce an error, right?
var myName = "Nick"; // …since the variable is declared after
You'd probably expect that console.log(myName)
to throw an error (since it's defined after), but what you actually get is undefined (the same as if you'd defined the variable without a value.) Here's how the code is actually executed, using variable hoisting:
var myName; // the declaration is moved to the top
console.log(myName);
myName = "Nick"; // …but the assignment stays where it was
A "block" in JavaScript is any code inside of curly braces ({}
). Let's take a look at how var
behaves within blocks:
var height = 1080,
isPortrait = true;
if (isPortrait) {
var height = 1920;
console.log(height); // this prints 1920
}
console.log(height); // this also prints 1920
You might have expected the last console.log(height)
to produce 1080
, since we're redefining height
inside of the block and the console.log
is outside of the if
block. At least the variable declaration inside of the if
block should have thrown an error since that variable was already defined. However, because var
isn't block-scoped, it will refer to the variable defined outside of the block even when referenced inside the block. Additionally, JavaScript will ignore if you try to redeclare the variable.
let
Now, let's explore how let
can help us overcome some of the issues associated with using var
.
Unlike with var
, the hoisting behavior doesn't happen with let
. This leads to more logical and predictable code:
console.log(myName); // now, this throws an error
let myName = "Nick"; // …since the variable is declared after
This forces us to declare our variables first, before attempting to use them:
let myName = "Nick"; // declare our variable first
console.log(myName); // now, we can access it
Remember back when we looked at hoisting with var
? The variable declarations were always moved to the top, while the value assignments stayed where they are. Since we don't have that now with let
, we're left with an area of code before the variable declaration known as a "Temporal Dead Zone":
/*
Any area of code before the declaration of the variable
is a "temporal dead zone" in reference to that variable.
In other words, that variable doesn't exist here.
*/
let myName = "Nick"; // oh good, we're out of the temporal dead zone
In reality, it's just a big name for a super-simple concept: you can't access a variable until you define it.
As you saw with var
, it completely ignores when we try to redeclare a variable inside of a block, but watch what happens with let
:
let height = 1080,
isPortrait = true;
if (isPortrait) {
let height = 1920; // variable is scoped to this block
console.log(height); // this prints 1920
}
console.log(height); // we're out of the block, this prints 1080
This strict behavior of let
leads to more predictable, more readable, and easier to debug code, by avoiding the forgiving behavior of defining variables with var
.
const
const
has all the same characteristics of let
, but with one main difference: you can't reassign its value. However, as we'll see below, its value can still change, under certain circumstances.
This name seems to suggest that when you define a variable with const
you'll have an unchangable, constant value (like in other languages), but as you work with const
, you'll find that isn't always the case.
What const
does is prevent you from reassigning the value (or reference) of the variable. If you use a primitive, like a string or a number, any changes to this value actually causes JavaScript to create a new value and reassign the reference.
const systemName = "Nintendo Entertainment System",
releaseDate = 1985;
// let's try to "reassign" these values
systemName = "Sega Genesis" // this will throw an error
releaseDate = 1989 // …and this will too
However, when you're working with objects (remember arrays and functions are objects too), which are reference-based, you'll find that as long as you don't reassign the variable, you can still make changes to the properties.
const system = {
name: "Nintendo Entertainment System",
releaseDate: 1985,
};
// let's try to "reassign" the properties of our variable
system.name = "Sega Genesis" // totally acceptable
system.releaseDate = 1989 // …and so is this
/*
Our "system" object now looks like this:
{
name: "Sega Genesis",
releaseDate = 1989,
}
*/
As you can see, we can change the properties of an object, since we're not reassigning (or repointing) the variable; it's still referring to the same place in memory. Just to dig in a little more, let's try to reassign the object:
const system = {
name: "Nintendo Entertainment System",
releaseDate: 1985,
};
// What happens if we try to reassign the object as a whole?
// this will totally throw an error…
system = {
name: "Sega Genesis",
releaseDate: 1989,
}
This behavior is something you'll want to be aware as you're working with const
. You might expect an object's values to be the same as when you declared them, and be surprised when they're not.
My rule of thumb: always use const
, unless you absolutely need to change the value, then use let
, but never use var
. I've read about some use cases where using var
would be beneficial, but I believe the downsides to using var
far outweigh any potential benefit. Using let
and const
exclusively will give you more stable code and help prevent a lot of unexpected side-effects.