Welcome back to the ES6 series, this week on block scoping and a new way of declaring variables. Let is the new var is the motto for today. If you are proficient with scoping and hoisting in JavaScript you know why you want to use let. If you are unsure or just want to repeat what hoisting is, then first head to Hoisting in a nutshell.

Last week we had a short recapitulation of the history and the future versions of JavaScript, and how you can use ES6 right now. If you missed it, here you can have a look.

Now let´s dive into our first feature. Consider the following example:

using var
1
2
3
4
5
6
7
8
function logging() {
var myVar = 'I got declared and assigned in function scope'
if (myVar) {
var myVar = 'I got reassigned';
console.log(myVar); //output: I got reassigned
}
console.log(myVar); //output: I got reassigned
}

We declared and assigned a variable myVar at the top of the function scope. Nothing special about this. Its the way you should declare variables and how the JavaScript engine is processing it (via hoisting), see Hoisting in a nutshell. Afterwards we use a simple if-statement to check if myVar is not undefined. Inside this block we assign a new value to myVar again using the declare & assign approach and log it to the console. Outside of the if-block again we want to log this variable. Now, what will be printed to the console? I think you know already… its two times I got reassigned. Its because the scope of a var variable is the enclosing function here. So if the JavaScript engine processes this, myVar gets reassigned and the console.log outside of the if-block will return the new value.
Now let´s change the code and fix this:

using let
1
2
3
4
5
6
7
8
function logging() {
var myVar = 'I got declared and assigned in function scope'
if (myVar) {
let myVar = 'I live in block scope';
console.log(myVar); //output: I live in block scope
}
console.log(myVar); //output: I got declared and assigned in function scope
}

Tip: if u use e.g. repl.it and want to use this let feature, place a "use strict"; at the beginning and encapsulate the function as IIEF like (function logging() {...})(), so it gets immediately invoked. Nice side-effect is that it avoids hoisting to and polluting the global scope.

IIEF
1
2
3
4
5
6
7
8
9
"use strict";
(function IIEF() {
var myVar = 'I got declared and assigned in function scope'
if (myVar) {
let myVar = 'I live in block scope';
console.log(myVar);
}
console.log(myVar);
})();

Another problem with var is within for-loops. Look at this code snippet:

for-loop
1
2
3
4
5
6
7
8
(function IIEF() {
var messages = ["0","1","2","3"];
for (var i = 0; i < messages.length; i++) {
setTimeout(function () {
console.log(messages[i]); //output: undefined four times
}, i * 2000);
}
})();

The reason why this does not work as intended is, that at the time the first setTimeout prints something to the console, the variable i is already 3. If you doubt, just place a console.log(i); at the beginning of the for-loop, like:

for-loop again
1
2
3
4
5
6
7
8
9
10
(function IIEF() {
var messages = ["0","1","2","3"];
for (var i = 0; i < messages.length; i++) {
console.log('outside setTimeout:', i);
setTimeout(function () {
console.log('inside:', i);
console.log(messages[i]); //output: undefined four times
}, i * 2000);
}
})();

The problem will be visible immediately.

  • outside setTimeout:0
  • outside setTimeout:1
  • outside setTimeout:2
  • outside setTimeout:3
  • inside: 4
  • undefined
  • undefined
  • undefined
  • undefined

var i as declaration is hoisted to the top scope of the function and by the time the first setTimeout is run, i is already 4 and therefor messages[4] is undefined. Before ES6 one could solve this using forEach, but now its as simple as replacing var with let. It will create a new binding of myVar each iteration. Just try it.

Take home message

scopes

  • let is block-scoped not the scope of the enclosing function
  • in for-loops a new binding of the let variable in each iteration is created

debugging

  • using the let variable before its declaration ill throw an ReferenceError (its called the `temporal dead zone)
  • redeclaring of a let variable will throw an error

Can i use let right now?

As of almost all new features the most secure way of using it, is the Babel or Google´s Traceur-transpiler. Or have a look for let at Knagax´ compatibility table.

So, have fun using ES6 today and see you next time, when we will discuss another feature of ES6, that will help you write less code much easier,namely Destructuring.