DEV Community

Cover image for ES6 - A beginners guide - New Variable scoping (Let and Const)
Stefan Wright
Stefan Wright

Posted on

ES6 - A beginners guide - New Variable scoping (Let and Const)

Hello again!

Welcome back! It's great to see you on this entry in the series. This time we are going to discuss the new variable scopes called let and const. In ES6 we can essentially forget about var and just use our new friends let and const to have much more control over our variables, ensure that they contain the exact data that we expect at the very point in our code execution.

So what are they?

Although less commonly used now with the introduction of ES6 we'll include var in the explanations below.

var

var can be seen as the most relaxed kind of declaration, it is left open for redeclaration, and also redefinition. For example:

var a = 1; // window.a = 1
var a = 2; // window.a = 2

var b = 1; // window.b = 1
b = 2; // window.b = 2
Enter fullscreen mode Exit fullscreen mode

Neither of these will cause the browser to throw an error or exception. Let's have a look at another var example which you may come across:

var a = 1;
function logVariable() {
  console.log(a);
  var a = 2;
}
logVariable(); // Returns undefined in a console log
console.log(a); // Returns 1 in a console.log
Enter fullscreen mode Exit fullscreen mode

Wait...what? How can we reference a before declaring it? This is what is known as Hoisting. Hoisting is where the JavaScript engine processes the var delcarations during compilation time but it does not assign it a value until the expression is executed so until that time you receive undefined as the return value.
Also, notice how the final console.log returns 1? This is because the first var is globally scoped, and the second is function scoped. So even though in the function we set a = 2, that was in the function scope and would only output 2 if we set a console.log after the assignment in the functions. As we had already set a = 1 at the global scope level when we console.log that outside the function it will use the global var.

So, what about let?

let is block scoped and so applies to everything inside

let a = 1;
console.log(a); // Returns 1 in a console.log
const logVariable = () => {
  console.log(a); // Uncaught ReferenceError
  let a = 2;
};
logVariable(); // Throws an exception
console.log(a); // Doesn't run because of the exception
Enter fullscreen mode Exit fullscreen mode

If you try to run the JavaScript above you will find that it throws an Uncaught ReferenceError, this is because whilst let's are hoisted but not initialized, they live in a "Temporal Dead Zone" (TDZ) meaning we cannot actually access it, thus throwing the ReferenceError.

Patricia has some great descriptions of Hoisting and the TDZ in her article here:



When we hit an exception like we see in the above example it stops the rest of the JavaScript in that block from functioning and thus we do not see the final console.log().

It is worth noting that let's can be re-assigned but not re-declared, for example if we re-assign the variable:

let a = 1;
a = 2;
console.log(a); // Returns 2 in a console.log
Enter fullscreen mode Exit fullscreen mode

but if we try to redeclare the variable as we see below, it will throw an exception:

let b = 1;
let b = 2;
console.log(b); // Throws a SyntaxError because b has already been declared
Enter fullscreen mode Exit fullscreen mode

Interestingly, if you run both of the above snippets at the same time, neither console.log's will output anything despite one of them referencing a variable already defined and assigned, this again is because of hoisting. The declarations are hoisted to the top, and the JS engine detects that there are two declarations for b throws the exception before attempting to execute anything in that block of code.

How about const?

The introduction of const is a nice one. It allow's us to add a level of security to our variables knowing they cannot be changed, well the changes are restricted...i'll go into that in a bit though. As with let, const's are hoisted and will also land in the TDZ during compilation, they also cannot be redeclared and are not available in the global scope.
A key difference between let and const is that const requires assignment at the point of declaration, you cannot create a const and then give it a value. Once given a value, that value is constant (almost).

const a; // Uncaught SyntaxError: Missing initializer in const declaration
const b = 1
b = 2 // Uncaught TypeError: Assignment to constant variable.
Enter fullscreen mode Exit fullscreen mode

I mentioned above that changes to a const are restricted as opposed to flat-out saying they cannot change. Look at the below example, you'll see that I create a const which is assigned an object (array's behave the same here), I can modify the contents of the object/array but I cannot completely change the assignment of the const itself. Let's get an example using an object:

const obj = {name: "Stefan"};
obj = {}; // Uncaught TypeError: Assignment to constant variable.
obj.name = "Bob";
console.log(obj) // Returns Bob in a console.log
Enter fullscreen mode Exit fullscreen mode

and an example using an array:

const arr = [1, 2, 3];
arr = [] // Uncaught TypeError: Assignment to constant variable.
arr.push(4) // You can push into a const array
arr[0] = 11 // You can also modify at the point of an array
console.log(arr) // Returns [11, 2, 3, 4] in a console.log

Enter fullscreen mode Exit fullscreen mode

Why??

Ultimately, const is a "Constant Reference" as opposed to a "Constant Value", this is because the declaration and assignment of a const is to a point in memory. Depending on the data type depends if the reference value is mutable or not. When you assign a string, boolen, number, or maybe even a function you are assigning a primitive value. When you assign an Object or an Array these are non-primitive. The assignment will be protected and y but the data inside it will not be protected. Primitive values are immutable whereas Objects and Arrays are mutable (can be changed). If you are using a const with an Object, and you want to lock those values in, you can use Object.freeze() to do this, see this example:

const obj = Object.freeze({name: "Stefan"});
obj.name = "Bob"; 
// If you are setting 'use strict' in your code then you will see
// Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'
// Else it will silently fail
console.log(obj) // Returns Stefan in a console.log
Enter fullscreen mode Exit fullscreen mode

Discussion (0)