ES6 let vs var vs const


ES6 let vs var vs const, oh my! ES6 is here, and developers are making use of it now. There are a lot of differences between ES6 and it’s predecessor ES5, and if you want to make use of the new features available to you, you need to learn about how things work in ES6. In this tutorial, we’ll start out at the very beginning in ES6 and examine the new let keyword, const keyword, as well as block scope in ES6. Buckle up, and let’s dig into the beginnings of ES6 right now!

The new let keyword in ES6

let is a new keyword for variable declaration in ES6 and one of the key things it does is to get rid of hoisting. Let’s see what that means.

ES5 style variable declaration and initialization (hoisting)

Here we have a simple script that logs out the value of a userid. Note that we are assigning the value of the userid after we log it out. In this instance we find the result of undefined. How can it even be undefined however? This is because of the way JavaScript uses hoisting, which means it moves the declaration to the top of any function, or in this example we are in the global scope. Let’s see how things might change if we use the let keyword in ES6.

ES6 style variable declaration and initialization (no hoisting)

This time around we make use of the let keyword, and notice the difference. We get a pretty gnarly error: ReferenceError: can’t access lexical declaration ‘userid’ before initialization. Why does this happen? This is because in ES6, using the let keyword removes the hoisting that usually takes place when a var is used instead. In modern JavaScript we are finding many software developers abandoning var and moving to let exclusively because it helps reduce hard to find errors and bugs.

Declaring a variable with let before use

Now we will still make use of the let keyword, but now let’s change the order of assigning the variable and logging it out to the console. We will assign before logging.

Ah yes, finally. We get what we have wanted, which is to properly assign the number 1 to our userid variable. It does seem a bit odd that they called it a let, since a let is still a variable, but alas this is the naming convention of the new variable type in ES6. Let it be, let it be, speaking words of wisdom, let it be…

Declaring a variable with let, but with no assignment

What about if we declare a variable using let, but do not assign anything to it before making use of it. What happens in this scenario? Let’s have a look.

Just like using var in ES5, if we declare but do not initialize a variable using let in ES6, then the value is set to undefined.

Block Scope in ES6!

ES6 now has block scope, and this is one of the new features that I and many others are very excited about. In ES5 all we had was function scope, and people came up with all manners of managing scope by way of esoteric and complex patterns like the IIFE Immediately Invoked Function Expression – Pronounced “iffy”.

This is an example making use of block scope in ES6. In this example here, we define userid two times. First it is defined in the global scope, then we define it in between the curly braces which denotes a block of code. When we log out the value of that variable outside of the block, note that we get the value of 10. This is what is called block scope. Changing those let keywords back to var gives us a very different result. Let’s see.

You can see that when we go back to using the var keyword, that assignment of 200 to userid inside the block basically overwrites the original variable declaration and initialization of 10 to userid. Of course just having a random block like this example is not really common, but this would apply to things like if and for statements which make use of blocks.

Can you access a variable outside the block when let is used?

What happens if we try to access a variable defined with let outside of the block it was used in? Let’s test it out now.

It looks like we get an error of ReferenceError: userid is not defined[Learn More]. The friendly folks at the Mozilla Developer Network tell us exactly what this means: There is a non-existent variable referenced somewhere. This variable needs to be declared, or you need make sure it is available in your current script or scope. What that tells us in this example is that when a block terminates, then any local variables defined in that block are no longer available.

The Temporal Dead Zone

We declare a function and assign the value of 1234 to the userid variable in that function. Note userid has not been officially declared yet. After the function, we declare userid and set it to null using let. updateUserId() get’s called, then we log out the value of userid. The result of this call to console.log is 1234. This is because the use of userid in the function happens in the Temporal Dead Zone. The compiler makes note of userid in the function even though it is not declared yet, and this is why it still works and logs out 1243.

ES6 Block Scope With For Loops

In looking at the output from this example ES6 script, we can see that the userid variable declared before the for loop using the let keyword, and the userid declared inside the for loop, also with the let keyword, are two different variables that each have their own values and own scope associated with them. As the for loop begins to run, it logs out the value of userid on each iteration. In this part of the script, userid is scoped to the for loop. When the loop finishes, that is the end of that block and outside of that block userid holds the value 321, which was declared and initialized at the top.

ES5 Closure vs ES6 Closure

Closure works a little differently in ES6 vs ES5, so let’s take a look at that here. We create an array which holds a function at each index in the array. We populate this array by using the .push() method of JavaScript. Therefore, on each iteration a function is appended to the array. The only thing each function does is to return the value of i. Finally, we log out the result of calling the function at the zero index of the array. You will see different results based on whether a var or a let is used to declare the i counter in the for loop. Let’s see.

Now, the function that lives at index 0 in the array is returning the value of i for the first iteration of the loop. You would think then that i should be 0, but we see 5 instead. Why is that? The reason is because a closure gets created over the i variable and at the end of the loop, i is set to 5. Because of this, the function is always going to return 5.

In this second iteration of using closure, we change it up and use let instead of var in the for loop. When we now log out the value of calling the function that exists at index 0 in the array, the result is 0. This is a little more along the lines of what you might expect. When we use let in a for loop, each iteration of the loop will get it’s own i variable. Any closures created will close over it’s own value of i unlike when using var.

Constants in ES6

In ES6, you can now make use of constants in your JavaScript code by use of the const keyword. A constant is used when you need to declare something to have a value that will not change at any time. Let’s see a few examples of using a constant and making use of const in ES6.

Using UPPERCASE and the const keyword ES6

Making use of all uppercase letters for the name of the constant is not actually required, but this is a convention in programming across almost any language so it makes sense to use it here in ES6 JavaScript as well.

Trying to use a const without initialization

When making use of the const keyword, you must initialize it at the same time. If you don’t, you’ll run into a problem like the example above shows.

Re-assigning a value to a constant

If you try to assign a new value to a constant after it has already been declared and initialized, you will get an error along the lines of what you see here: TypeError: invalid assignment to const `TAXES’. Once a constant is declared and initialized, that’s it, you can’t update or change it’s value in that scope.

Constants in different scopes

Speaking of constants and scopes, let’s look at an example of that now.

In this example, we can see that the TAXES constant actually gets assigned a value twice. No error is thrown, and we get a value of ‘Too High’ when we log out the value of TAXES. Wat? Well, what happens is that similar to let, a const has the concept of block scope. Therefore, when the script delves into the if branch, it does indeed assign ‘Way Too High’ to the TAXES constant. It just so happens however that this instance of TAXES is completely separate from the TAXES constant defined at the beginning of the script. There are two scopes in play here, the block scope of the if statement, and the outer global scope. Constants of the same name can peacefully coexist in different scopes, even with the same name. There are no problems with that.

ES6 let vs var vs const Summary

If you’re new to ES6, hopefully you learned a few things about the new let and const keywords, as well as block scope. I’ll admit, covering the low level basics is not as fun as tutorials where we get to create something more hands on – but getting the basics down of any new technology is key to building a solid foundation for which to build our knowledge further. This was a good introduction and we touched on using let and understanding the differences that exist to using var. The let keyword does not hoist, so this is an entirely new behavior we must learn, but it also matches more closely how we might thing about code in general. We also saw that we have a new const keyword which allows us to define constants in our programs, similar to how we might in other languages that support constants. Finally, we took a look at block scope. Block scope is a welcome addition to JavaScript in ES6, as it helps developers to better maintain state and reason about their applications.