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
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
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
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
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
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.
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
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
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
Top comments (0)