The new let
and const
in ES2015 (aka "ES6") have four major differences compared with the venerable var
:
They have block scope
They aren't hoisted (well, they're sort of hoisted, but in a useful way)
Repeated declarations are errors
When used at global scope, they don't create properties of the global object (despite creating global variables; this is a new concept as of ES2015)
Block scope
var
variables exist throughout the function they're declared in (or globally, if declared globally), they aren't confined to the block they're in. So this code is valid:
function foo(flag) {
a = 10;
if (flag) {
var a = 20;
}
return a;
}
console.log(foo(false)); // 10
console.log(foo(true)); // 20
a
is defined regardless of whether flag
is true and it exists outside the if
block; all three a
s above are the same variable.
That's not true of let
(or const
):
function foo(flag) {
if (flag) {
let a = 10;
}
return a; // ReferenceError: a is not defined
}
console.log(foo(true));
a
only exists inside the block it's declared in. (For for
statements, a declaration within the ()
of the for
is effectively considered part of the block.) So outside the if
, a
doesn't exist.
let
and const
declarations can shadow declarations in an enclosing scope, e.g.:
function foo() {
let a = "outer";
for (let a = 0; a < 3; ++a) {
console.log(a);
}
console.log(a);
}
foo();
That outputs
0
1
2
outer
...because the a
outside the for
loop isn't the same a
as the one inside the for
loop.
Hoisting
This is valid code:
function foo() {
a = 5;
var a = a * 2;
return a;
}
Bizarre-looking, but valid (it returns 10), because var
is done before anything else is done in the function, so that's really:
function foo() {
var a; // <== Hoisted
a = 5;
a = a * 2; // <== Left where it is
return a;
}
That's not true of let
or const
:
function foo() {
a = 5; // <== ReferenceError: a is not defined
let a = a * 2;
return a;
}
You can't use the variable until its declaration. The declaration isn't "hoisted" (well, it's partially hoisted, keep reading).
Earlier I said
- They aren't hoisted (well, they're sort of hoisted, but in a useful way)
"Sort of"? Yes. A let
or const
declaration shadows an identifier throughout the block in which it appears, even though it only actually takes effect where it occurs. Examples help:
function foo() {
let a = "outer";
for (let x = 0; x < 3; ++x) {
console.log(a); // ReferenceError: a is not defined
let a = 27;
}
}
Note that instead of getting "outer"
in the console, we get an error. Why? Because the let a
in the for
block shadows the a
outside the block even though we haven't gotten to it yet. The space between the beginning of the block and the let
is called the "temporal dead zone" by the spec. Words aren't everybody's thing, so here's a diagram:
Repeated declarations
This is valid code:
function foo() {
var a;
// ...many lines later...
var a;
}
The second var
is simply ignored.
That's not true of let
(or const
):
function foo() {
let a;
// ...many lines later...
let a; // <== SyntaxError: Identifier 'a' has already been declared
}
Globals that aren't properties of the global object
JavaScript has the concept of a "global object" which holds various global things as properties. In loose mode, this
at global scope refers to the global object, and on browsers there's a global that refers to the global object: window
. (Some other environments provide a different global, such as global
on NodeJS.)
Until ES2015, all global variables in JavaScript were properties of the global object. As of ES2015, that's still true of ones declared with var
, but not ones declared with let
or const
. So this code using var
at global scope, on a browser, displays 42:
"use strict";
var a = 42; // Global variable called "a"
console.log(window.a); // Shows 42, because a is a property of the global object
But this code shows undefined
for the properties, because let
and const
at global scope don't create properties on the global object:
"use strict";
let a = 42; // Global variable called "a"
console.log(a); // 42 (of course)
console.log(window.a); // undefined, there is no "a" property on the global object
const q = "Life, the Universe, and Everything"; // Global constant
console.log(q); // "Life, the Universe, and Everything" (of course)
console.log(window.q); // undefined, there is no "q" property on the global object
Final note: Much of the above also holds true if you compare the new ES2015 class
(which provides a new, cleaner syntax for creating constructor functions and the prototype objects associated with them) with function declarations (as opposed to function expressions):
class
declarations have block scope. In contrast, using a function declaration within a flow-control block is invalid. (It should be a syntax error; instead, different JavaScript engines handle it differently. Some relocate it outside the flow-control block, others act as though you'd used a function expression instead.)
class
declarations aren't hoisted; function declarations are.
- Using the same name with two
class
declarations in the same scope is a syntax error; with a function declaration, the second one wins, overwriting the first.
class
declarations at global scope don't create properties of the global object; function declarations do.
Just as a reminder, this is a function declaration:
function Foo() {
}
These are both function expressions (anonymous ones):
var Foo = function() {
};
doSomething(function() { /* ... */ });
These are both function expressions (named ones):
var Foo = function Foo() {
};
doSomething(function Foo() { /* ... */ });