Note: This is part of my es6 series of posts.
The let
keyword declares a variable that is strictly scoped to the current block, statement, or expression where it is defined. This is in contrast to var
declarations which are scoped to the current function. An additional difference with var
is that let
variables are not hoisted to the top of the scope and they can only be used at a point in the code after they have been defined.
const
variables share all these characteristics with the additional restriction that redeclaring them will generate an error, and changing their value once declared will fail silently.
As a rule of thumb const
provides the strictest usage contract and clearly signals a narrow intent that the variable will not be redeclared or subsequently have its value reassigned, therefore const
should be used in preference to let
and var
wherever appropriate.
Examples of "blocks" that let
and const
variables are scoped to include if
statements, for
loop headers and bodies and naked {}
blocks.
Block Scope
Attempting to access a let
or const
variable outside the block it's declared in will throw an error. Note the use of a naked {}
block in this example to arbitrarily create a new scope.
var a = 1;
{
let b = 2;
}
console.log(a); // 1
console.log(b); // ReferenceError, b is undefined
Hoisting
Unlike var
declarations which are hoisted to the top of their enclosing scope let
and const
declarations may only be accessed after they've been declared. let
and const
variables are said to be in the scope's TZD (temporal dead zone) before they've been declared, and any attempt to read or write them beforehand will generate an error.
⚠️ Most transpilers currently don't handle this behavior fully to-spec, so the above example will probably only error in a native ES6 environment.
{
console.log(foo); // undefined
console.log(bar); // ReferenceError: bar is in the 'TDZ'
var foo = 'foo';
let bar = 'bar';
}
Loop Scope
When let
is used in a for
loop header a new i
is scoped for each iteration of the loop. This makes writing async code in loops more intuitive since the closure doesn't need to be created manually. This can also help with traditionally counter-intuitive tasks such as applying click event handlers in a loop.
for (var i=1; i<=5; i++) {
setTimeout(function(){
console.log(i);
}, i*100);
}
// 6,6,6,6,6
for (let i=1; i<=5; i++) {
setTimeout(function(){
console.log(i);
}, i*100);
}
// 1,2,3,4,5
Implicit Scope Creation
Using let
within an if
block implicitly creates a new scope. This is a hazard of using let
. The new scope is easily spotted in the simple example above, but when code becomes more complicated hunting for new scopes created by let
could become a cognitive burden. A rule of thumb is to place let
declarations at the top of their enclosing block to clearly signpost their use and also avoid being bitten by the TDZ.
if ( foo ) {
// We're in the same scope as outside the 'if'
}
if ( foo ) {
// We're in a new scope
let a = 1;
}
Read-Only const
As mentioned, reassign a value to a constant will silently fail while redeclaring the constant will throw an error.
const foo = 'foo';
foo = 'bar' // Silently fails, foo is still equal to 'foo'
const foo = 'bar' // TypeError, foo has already been defined
However constants are not immutable, therefore the properties of non-primitive values defined as a constant may be manipulated freely.
const foo = {a: 1};
foo.a = 2;
foo.a; // 2
Happy coding 😃.
Top comments (0)