ES6 provides two new ways of declaring variables:
const, which mostly replace the ES5 way of declaring variables,
let works similarly to
var, but the variable it declares is block-scoped, it only exists within the current block.
var is function-scoped.
In the following code, you can see that the
tmp only exists inside the block that starts in line A:
const works like
let, but the variable you declare must be immediately initialized, with a value that can’t be changed afterwards.
for-of creates one binding (storage space for a variable) per loop iteration, it is OK to
const-declare the loop variable:
The following table gives an overview of six ways in which variables can be declared in ES6 (inspired by a table by kangax):
|Hoisting||Scope||Creates global properties|
||Temporal dead zone||Block||No|
||Temporal dead zone||Block||No|
const create variables that are block-scoped – they only exist within the innermost block that surrounds them. The following code demonstrates that the
tmp only exists inside the block of the
var-declared variables are function-scoped:
Block scoping means that you can shadow variables within a function:
constcreates immutable variables
Variables created by
let are mutable:
Constants, variables created by
const, are immutable – you can’t assign different values to them:
constdoes not make the value immutable
const only means that a variable always has the same value, but it does not mean that the value itself is or becomes immutable. For example,
obj is a constant, but the value it points to is mutable – we can add a property to it:
We cannot, however, assign a different value to
If you want the value of
obj to be immutable, you have to take care of it, yourself. For example, by freezing it:
Keep in mind that
Object.freeze() is shallow, it only freezes the properties of its argument, not the objects stored in its properties. For example, the object
obj is frozen:
But the object
obj.foo is not.
constin loop bodies
const variable has been created, it can’t be changed. But that doesn’t mean that you can’t re-enter its scope and start fresh, with a new value. For example, via a loop:
There are two
const declarations in this code, in line A and in line B. And during each loop iteration, their constants have different values.
A variable declared by
const has a so-called temporal dead zone (TDZ): When entering its scope, it can’t be accessed (got or set) until execution reaches the declaration. Let’s compare the life cycles of
var-declared variables (which don’t have TDZs) and
let-declared variables (which have TDZs).
var variables don’t have temporal dead zones. Their life cycle comprises the following steps:
varvariable is entered, storage space (a binding) is created for it. The variable is immediately initialized, by setting it to
Variables declared via
let have temporal dead zones and their life cycle looks like this:
letvariable is entered, storage space (a binding) is created for it. The variable remains uninitialized.
const variables work similarly to
let variables, but they must have an initializer (i.e., be set to a value immediately) and can’t be changed.
Within a TDZ, an exception is thrown if a variable is got or set:
If there is an initializer then the TDZ ends after the initializer was evaluated and the result was assigned to the variable:
The following code demonstrates that the dead zone is really temporal (based on time) and not spatial (based on location):
ReferenceErrorfor a variable in the TDZ
If you access a variable in the temporal dead zone via
typeof, you get an exception:
Why? The rationale is as follows:
foo is not undeclared, it is uninitialized. You should be aware of its existence, but aren’t. Therefore, being warned seems desirable.
Furthermore, this kind of check is only useful for conditionally creating global variables. That is something that you don’t need to do in normal programs.
When it comes to conditionally creating variables, you have two options.
Option 1 –
This option only works in global scope (and therefore not inside ES6 modules).
Option 2 –
There are several reasons why
let have temporal dead zones:
constwork properly is difficult. Quoting Allen Wirfs-Brock: “TDZs … provide a rational semantics for
const. There was significant technical discussion of that topic and TDZs emerged as the best solution.”
letalso has a temporal dead zone so that switching between
constdoesn’t change behavior in unexpected ways.
undefinedbefore its declaration then that value may be in conflict with the guarantee given by its guard.
Sources of this section:
constin loop heads
The following loops allow you to declare variables in their heads:
To make a declaration, you can use either
const. Each of them has a different effect, as I’ll explain next.
var-declaring a variable in the head of a
for loop creates a single binding (storage space) for that variable:
i in the bodies of the three arrow functions refers to the same binding, which is why they all return the same value.
let-declare a variable, a new binding is created for each loop iteration:
This time, each
i refers to the binding of one specific iteration and preserves the value that was current at that time. Therefore, each arrow function returns a different value.
const works like
var, but you can’t change the initial value of a
Getting a fresh binding for each iteration may seem strange at first, but it is very useful whenever you use loops to create functions that refer to loop variables, as explained in a later section.
var creates a single binding:
const creates one immutable binding per iteration:
let also creates one binding per iteration, but the bindings it creates are mutable.
for-in loop works similarly to the
The following is an HTML page that displays three links:
What is displayed depends on the variable
target (line B). If we had used
var instead of
const in line A, there would be a single binding for the whole loop and
target would have the value
'vielleicht', afterwards. Therefore, no matter what link you click on, you would always get the translation
const, we get one binding per loop iteration and the translations are displayed correctly.
let-declare a variable that has the same name as a parameter, you get a static (load-time) error:
Doing the same inside a block shadows the parameter:
var-declaring a variable that has the same name as a parameter does nothing, just like re-declaring a
var variable within the same scope does nothing.
If parameters have default values, they are treated like a sequence of
let statements and are subject to temporal dead zones:
The scope of parameter default values is separate from the scope of the body (the former surrounds the latter). That means that methods or functions defined “inside” parameter default values don’t see the local variables of the body:
window in web browsers,
global in Node.js) is more a bug than a feature, especially with regard to performance. That’s why it makes sense that ES6 introduces a distinction:
Note that the bodies of modules are not executed in global scope, only scripts are. Therefore, the environments for various variables form the following chain.
The following code demonstrates the hoisting of function declarations:
Classes not being hoisted may be surprising, because, under the hood, they create functions. The rationale for this behavior is that the values of their
extends clauses are defined via expressions and those expressions have to be executed at the appropriate times.
I recommend to always use either
const. You can use it whenever a variable never changes its value. In other words: the variable should never be the left-hand side of an assignment or the operand of
--. Changing an object that a
constvariable refers to is allowed:
You can even use
const in a
for-of loop, because one (immutable) binding is created per loop iteration:
Inside the body of the
x can’t be changed.
let– when the initial value of a variable changes later on.
If you follow these rules,
var will only appear in legacy code, as a signal that careful refactoring is required.
var does one thing that
const don’t: variables declared via it become properties of the global object. However, that’s generally not a good thing. You can achieve the same effect by assigning to
window (in browsers) or
global (in Node.js).
An alternative to the just mentioned style rules is to use
const only for things that are completely immutable (primitive values and frozen objects). Then we have two approaches:
constmarks immutable bindings.
constmarks immutable values.
I lean slightly in favor of #1, but #2 is fine, too.