ECMAScript 6 let vs var

ES6 has introduced a new way to declare variables, the let keyword. This means we now have two methods of declaring variables, var and let. But how does the let keyword differ from the var keyword?

Basically this boils down to scope – let uses block scope and var uses function scope.

But what’s the difference between block scope and function scope?

Function Scope

Function scope means that the variable is available throughout the function in which it was declared. For example:

(function() {
    var x = 1;
    {
        var x = 2;
        console.log(x);  // returns 2
    }
    console.log(x);  // returns 2
}());

In the above code, although it looks like we are declaring a new variable with var x = 2; what we are actually doing is changing the value of the existing x variable.

This is made clear with the second console.log statement, which outputs the value 2. This demonstrates that var x = 2; does not declare a new variable, and that the x variable within the block statement is indeed a reference to the existing x variable. If var was block scoped, the second console.log statement would have output the value 1.

Contrast this to an example with let – which is block scoped.

Block Scope

Block scope means that the variable is scoped to the block in which it was declared – which is not necessarily the function in which it was declared:

(function() {
    let x = 1;
    {
        let x = 2;
        console.log(x); // returns 2
    }
    console.log(x); // returns 1
}());

In this example notice how the second console.log statement correctly outputs the value 1.

This demonstrates that let is indeed block scoped. The let x = 2; statement declares a new variable and initialises it with the value 2. This new variable is scoped to the block, which can be verified by the second console.log statement which returns the value of the first x variable – the value 1.

What about mixing the two?

What happens when we declare the first occurrence of x with var, and the second occurrence with let? The first x will be scoped to the function, because var is function scoped, and the second x is scoped to the block, because let is block scoped.

(function() {
    var x = 1;
    {
        let x = 2;
        console.log(x); // returns 2
    }
    console.log(x); // returns 1
}());

As you can see above, this works as expected. This is because the second occurrence of x is scoped to the block, this creates a new variable within a new scope and it’s this variable that we access within this block – so our changes are not affecting the function scoped x.

How did this work before let?

Before let, you would have to use function scope:

(function() 
    var x = 1;
    (function() {
        var x = 2;
        console.log(x); // returns 2
    }());
    console.log(x); // returns 1
}());

In this example, we utilise the fact that var is function scoped and create a new immediate anonymous function.

We then declare the second x variable within this new scope. This new function scope provides encapsulation and ensures that the second var statement declares a new variable within a new scope – and does not mutate the existing x variable.