Block Binding - ES6

Summary

One of the major Javascript feature or distinction from other C-based programming language is that there's no block level scope in Javascript. Things changed in ES6.

Problems in ES5

var is the only keyword used to declare a variable or function in Javascript before. When we declare a variable or function, the initialization of them be moved to the top of the current scoping or global environment, this is called hoisting. Let's take a look at an example.

function func(a) {
    console.log(b); // undefined
    var b = a;
    console.log(b); // a
 }

In most of other languages, first console.log(b) will throw a ReferenceError, but this is acceptable in Javascript because of hoisting. This function is equavalent to the following.

function func(a) {
    var b;
    console.log(b); // undefined
    b = a;
    console.log(b); // a
 }

This will explain why first console.log won't throw an error. hoisting in Javascript makes block level scoping become impossible. See following example.

function func(condition) {
   if (condition) {
     var value = 1;
     console.log(value); // 1 if condition=true
     function func1() {}
   } else {
     console.log(value); // expecting an error here if condition=false
   }
 }

Another famous example can clearly indicate the confusion that lack of block level scope introduces.

for(var i=0; i<10; i++) {
   // do something
 }
 console.log(i); // 10

Variables i defined inside the for-loop could still be accessible outside the for-loop. If var is not used when declaring a variable, the variable will be added to the global environment, see example.

function add(num1, num2) {
    sum = num1 + num2;
    return sum;
}
console.log(add(10, 20)); // 30
console.log(sum); // 30
console.log(window.sum === sum); // true

In this example, sum is not initialized with var, so it is added to the global enviroment. To sum up, lack of block level scope introduces a lot of confusion and could easily lead to a lot of bugs if they are not well taken care of.

Solutions in ES6

let and const are introduced to replace var in ES6. Although var is still supported in ES6, but it is highly recommeded to use let and const instead. Both let and const are block level declaration which means the variables won't be accessible outside of the block level. Block level are created in two places:

  • inside a function
  • inside a curly braces ({})
let

let is used to declare a variable whose value might be changed later on and it has almost the same syntax as var, the only difference is that the variables declared with let will only be accessible in the block. See an example below.

function func(condition) {
    console.log(value); // Throw a reference error
    if (condition) {
        console.log(value); // Throw a reference error
        let value = 1;
        console.log(value); // 1
        // do something
    } else {
        // do something else
        console.log(value); // throw a reference error
    }
    console.log(value); // throw a reference error
}

From this example, we can clearly see that the variable declared by let will only be accessible after the initialization inside that scope. let also solves the problem in the for-loop.

for(let i=0; i<10; i++) {
    // do something here
}
console.log(i); // throw a reference error
TDZ

Variables declared by let will be put into the Temporal Dead Zone a.k.a TDZ and it will be taken out the TDZ once the compiler comes to the line of variable declaration. If the program tries to access the variables in the TDZ, a ReferenceError will be thrown.

No Redeclaration

Remember in ES5, if we declare a variable twice with var, no errors will be thrown, but if an identifier has been declared and is declared again inside the same scope, a SyntaxError will be thrown, see example below.

let a = 1;
let a = 2; // SyntaxError: Duplicate declaration "a"

but

function func(condition) {
    let a = 1;
    if (condition) {
        let a = 2;
    } else {
        let a = 3;
    }
    console.log(a); // 1
}

works fine.

const

Apart from let, const is another keyword used to declare a variable but const will only used if the variable's value won't be modified later on, that is to say, variables declared by const are constants. Thus, every const variables must be initialzied on declaration. See example below.

const MAXVALUE = 0;
MAXVALUE = 1; // A SyntaxError will be thrown
const MINVALUE; // A SyntaxError will be thrown.

If a const variable is initialzied with an object, the object can be modified. In theory, A const declaration prevents modification of the binding, not of the value. For example:

const person = {
    name: 'Wen Zhu'
};
person.name = 'Zhu Wen'; // No problem!
// Throw a SyntaxError
person = {
    name: 'Another Person';
};
const in loops

const can not be used in normal for loop, but could be used in for...in and for...of, for example.

// SyntaxError
for(const i=0; i<10; i++) {
    // do something
}
// Works
const obj = {
    prop1: 1,
    prop2: 2
};
for(const key in obj) {
    // do something
}
// Works
const arr = [1, 2, 3];
for(const item of arr) {
    // do something
}
Global Block Bindings

In ES5, if we use var in the global environment, then it will potentially overwritten the builtin glable variables, for example:

var RegExp = 'Hello';
console.log(window.RegExp); // Hello

But with let and const, the global variables won't be overwritten but shadowed.

let RegExp = 'Hello';
console.log(window.RegExp === RegExp); // false

Although the global variables won't be overwritten, but we should try out best to avoid those reserved keyword and builtin variable names.

When to use what

This is a hot topic in the community, but the emerging opinion is we should use const by default and use let when this variable will be modified later. The rationale behind is that most variables should be modified after initialization which is the same idea behind Functional Programming.