Table of contents
Open Table of contents
Introduction
This article is the continuation of the previous article I published on hoisting, Hoisting in JavaScript: A Complete Guide, Part 1. I will advise you first read it before engaging with this article. In the previous article I explained hoisting with well-thought-out examples. We looked at how variables declared with the var
, let
, and const
keywords are hoisted, including function declarations and function expressions. We also established hoisting rules, which served as our guide for explaining hoisting in JavaScript. In this article, I am going to deep dive into how variables declared with the ES6 let
and const
keywords are hoisted, particularly the Temporal Dead Zone (TDZ), and I will also explain how classes are hoisted. Happy reading!
What is Temporal Dead Zone (TDZ)?
The term “Temporal Dead Zone“ is mostly attributed to the behavior of let
and const
declarations in the ECMAScript 2015 (ES6) when they are referenced before declaration. In JavaScript, all variable declarations (var
, const
, and let
) are hoisted at the top of their containing scope, but are not initialized the same. Confused? Don’t sweat it, I will drive the point home. Recall in the part one of this article I said that, variables declared with the var
keyword are hoisted/lifted without their initialized values, but rather the compiler assigns a default value to them. Can you recall the default value? Yes, it’s undefined
.
Consider this example:
console.log(pet); // undefined
var pet = "Cat 🐱";
The result of running the above code will produce undefined
on the browser console. This is because the JavaScript engine lifted the variable, pet above in the global scope and initializes a default value of undefined
to it. Check the part one of this article for an in-depth explanation.
Have a look at these examples where the variable pet is declared with let
and const
keywords.
console.log(pet); // ReferenceError
let pet = "Cat 🐱";
console.log(pet); // ReferenceError
const pet = "Cat 🐱";
The compiler threw a ReferenceError
exception. Have a close look at the error message for both codes which is the same: “Cannot access ‘pet’ before initialization“. The error message in each case signifies that the compiler is aware of the variable, pet, but cannot read/access it due to the fact that it is not initialized. Meaning, variables declared with the let
keyword are hoisted just like var
, but they are not initialized a default value like the way var
is initialized a default value of undefined
. Because let
are not initialized a default value, they remain in a state where they are unreachable if referenced before their declaration, which is known as Temporal Dead Zone (TDZ).
Temporal Dead Zone (TDZ) as defined by Kyle Simpson is the time window where a variable exists but is still uninitialized, and therefore cannot be accessed in any way.
Some developers have wrongly believed that let
and const
declared variables are not hoisted because of the ReferenceError
exception the compiler throws when they are referenced before their declaration. If they are not hoisted, the exception will still be ReferenceError
, but with an entirely different message, one that the compiler throws when accessing a variable that is not declared. E.g “pet is not defined“, just like the way we have it below.
console.log(pet);
Key Takeaways
- When you try to access a variable that is not declared, you get a
ReferenceError
exception with a message telling you that the variable is not defined. - When you try to access a variable declared with the
let
orconst
keyword before its declaration, you get aReferenceError
exception with a message telling you that you cannot access the variable before initialization. - When you try to access a variable declared with the
var
keyword before its declaration, you getundefined
.
How are ES6 Classes Hoisted?
We have covered how variables declarations, function declarations, and function expressions are hoisted. We had a deep dive of their nuances and gotchas and how to avoid them. Next, we are going to take a look at ES6 classes.
ES6 Classes
ES6 classes are a syntactic way of creating the good ol’ function constructors with additional enhancements. They are hoisted, and behave differently based on the way they are declared in the code.
Class Declarations
Class declaration is the conventional way of creating classes in JavaScript. Classes are declared in this fashion: the class
key word followed by an identifier and a set of opening and closing brackets. E.g. class Car {}
. Class declarations are hoisted the same way let
and const
are hoisted. They are lifted at the top of their containing scope and remain in a state where they are unreachable if referenced before they are declared. Their entire class definition is not lifted alongside their identifier neither are they initialized a default value when hoisted.
Consider:
const sixtus = new User();
class User {}
Just like the way the JavaScript engine threw a ReferenceError
exception when we tried to access the variable pet before it’s declaration, the engine did same here with a message “Cannot access ‘User’ before initialization“. This means that the engine is aware of the class User
, but because it was referenced before its declaration i.e., in its TDZ state, it threw a ReferenceError
exception.
Class Expressions
Like functions, JavaScript classes are first-class citizens, which means they can be passed into a function as arguments, returned from a function and assigned to a variable. Class expression is a way of defining a class where a variable declared is initialized to a class. Just like the way we have named and unnamed (anonymous) function expressions, we also have named and unnamed class expressions. Below are examples of class expressions.
var User = class {}; // Declared with `var`. Anonymous class expression
const Animal = class {}; // Decalared with `const`. Anonymous class expression
let Car = class {}; // Declared with `let`. Anonymous class expression
let User = class UserCls {}; // Named class expression
Recall the hoisting rules we established in part one of this article? Class expressions take on the hoisting rule of the variable they are initialized to. In reality, variables are what are hoisted in class expressions. Note these two points about hoisting for class expressions:
- If a variable is declared with the
var
keyword and initialized with a class expression (named or unnamed), the variable will be hoisted (lifted) to the top of its containing scope without its initialized value (class expression) and the JavaScript engine will initialize a default value ofundefined
to the variable. - If a variable is declared with
const
orlet
keyword and initialized with a class expression (named or unnamed), the variable will be hoisted (lifted) to the top of its scope without its value, and it will not be initialized a default value by the JavaScript. Accessing the variable before its declaration will result to aReferenceError
exception.
Consider this code example where the variable “User” is declared with the var
keyword:
console.log(User); // undefined
var john = new User(); // TypeError
var User = class {}; // Anonymous class expression
The first log of undefined
on the browser console is an indicator that the JavaScript engine hoisted the variable “User” and initialized it a default value of undefined. The second log, which is a TypeError
exception thrown by the engine signifies that since the variable “User” is of type undefined
before its declaration and initialization, It cannot be instantiated. Only constructor functions and classes can be instantiated in JavaScript.
Let’s look at one more code example declared with let
keyword
let john = new User(); // ReferenceError
let User = class {}; // Anonymous class expression
Guess why the JavaScript engine threw the above error? Rule three of our hoisting rules explains it all. The variable “User” is hoisted without it’s initialized value (class expression), and because it’s declared using the let
keyword, it will not be initialized a default value. It remains in a state where it’s inaccessible when referenced before its actual declaration. Can you still recall the name of that state where variables declared with let
and const
remain before their declaration? Don’t feel bad if you can’t recall it. Most times to have a solid grasp of these subtle concepts, we have to re-read the necessary materials to allow them sink. The name of that state is called Temporal Dead Zone (TDZ).
Conclusion
We took some time to go through Temporal Dead Zone (TDZ) and hoisting in ES6 classes. We identified that variables declared with the let
and const
keywords as well as class declarations enter a state known as Temporal Dead Zone when they are hoisted, and accessing them in that state will result to a ReferenceError
exception. Furthermore, we discovered that class expressions take on the hoisting rule of the variable they are initialized to.
I will advise you revisit the hoisting rules we established in part one of this article, reread them and use them as a reference while practicing. My advice at this point is to get your hands dirty by practicing to further internalize this concept called hoisting.
If you think there is any statement or explanation that I made in this article that is not correct or need improvement, please reach out to me via any of my social links. I am still on my journey to a perfectionist in my craft as a programmer and will like to embrace any advantage that will help me get better. My main goal is to provide quality educational content for my esteemed readers.
Thank you!