Hoisting and TDZ in Practice
Maybe it didn't seem like it, but hoisting is actually a fairly straightforward process. Let's go through the different scenarios here, starting with variables.
Let's create three variables using the three different declaration methods.
var me = 'John';
let job = 'Software Engineer';
const year = 1993;
Now, let's try to use all of these three variables before declaring them, and see what happens.
console.log(me);
console.log(job);
console.log(year);
var me = 'John';
let job = 'Software Engineer';
const year = 1993;
In our browser console, we can see that the first console.log
result in undefined
, and that's because variables declared with var
are hoisted, but they are hoisted to the value of undefined
. Therefore when we try to access them, undefined
is exactly the result that we get.
Then, on the contrary, we have the let
variable that results in an error saying we cannot access job
before initialization. That's exactly the kind of error that we had in the last lecture. The origin of this error is the fact that the job
variable is still in the temporal dead zone here at this point.
console.log(me);
console.log(job);console.log(year);
var me = 'John';
let job = 'Software Engineer';
const year = 1993;
Remember that the temporal dead zone of a variable declared with a let
or const
, starts from the beginning of the current scope, and so that is basically lines one to four until the point of the code where it is defined. Line six.
This means that on line 2 when we try to log the job
variable, it is still in the temporal dead zone, and the same is true for the year
variable, which gives us the same error.
Great! Let's now move on and have some examples with functions. We will create one function expression, one function declaration, and an arrow function.
// Function Declaration
function addFuncDec(a, b) {
return a + b;
}
// Function Expression
const addFuncExp = function (a, b) {
return a + b;
};
// Arrow Function
const addArrowFunc = (a, b) => a + b;
Let's now try to use these functions before they are defined. We will start by logging the result of our function declaration.
console.log(addFuncDec(3, 3));
// Function Declaration
function addFuncDec(a, b) {
return a + b;
}
// JS Code
In the console, we can see that we get the result of six, which proves that we could call the function declaration before it was actually defined in the code.
Now let's try the same for the other two functions: expression and arrow. I guess you've already anticipated what's going to happen.
console.log(addFuncDec(3, 3));
console.log(addFuncExp(3, 3));console.log(addArrowFunc(3, 3));
// JS Code
We can see an error in the console saying it cannot access the addFuncExp
before initialization, which is the same error we got before with the let
and const
variables.
That's because the function addFuncExp
is simply a const
variable too, which means that it's now also in the temporal dead zone. So again, we are simply assigning a function value to the variable addFuncExp
. And since this variable (addFuncExp
) was defined with const
, it is now in a temporal dead zone, and therefore, we get this same error message as before.
The same, of course, will happen with the arrow function. Now you will notice that if we change const
to var
on both the function expression and arrow function, we get the below error.
We get this new error message because, as you already know, any variables declared with var
will be hoisted and set to undefined
. The addFuncExp
function is a variable declared with var
, so it is undefined right now.
Then when we try to run the below line of code:
addFuncExp(3, 3);
We are trying to call undefined
basically. That is, we are doing something like this:
undefined(3, 3);
Which if we try to run in the console, we will get a similar error saying undefined
is not a function.
We can also try to check the value of addFuncExp
to be sure that its value is undefined
by simply logging it to the console.
console.log(addFuncDec(3, 3));
console.log(addFuncExp);console.log(addFuncExp(3, 3));
console.log(addArrowFunc(3, 3));
// JS Code
Once more, that is because we declared both addFuncExp
and addFuncArrow
with var
.
Now that we covered all the rules here and saw how hoisting works let's have an example to demonstrate a pitfall of hoisting. It's a mistake that we can easily make if we're not careful and at the same time use var
, to declare our variables.
Let's start by declaring a fictional function declaration called deleteShoppingCart
to delete the shopping cart on an e-commerce website or application.
function deleteShoppingCart() {
// Dangerous function ⚠️ that we should not be calling without care.
console.log('All products deleted!');
}
Next, let's declare a variable that contains the number of products. We will use the var
keyword to declare our variable, and this will then show you why we should not use var
once more.
var numberOfProducts = 10;
function deleteShoppingCart() {
// Dangerous function ⚠️ that we should not be calling without care.
console.log('All products deleted!');
}
Let's now write some logic to delete the shopping cart whenever the number of products is zero. We already know that zero is a false value, so we can write this:
if (!numberOfProducts) deleteShoppingCart();
var numberOfProducts = 10;
function deleteShoppingCart() {
// Dangerous function ⚠️ that we should not be calling without care.
console.log('All products deleted!');
}
Back to our browser, below is the result we get in the console! 😳
We get all products deleted even though numberOfProducts
is 10. The question now is: Why did that happen? 🤔
Well, it is because of hoisting. At this point in our code, i.e. in the if condition;
if (!numberOfProducts) deleteShoppingCart();
//JS Code
The numOfProducts
variable is not 10; instead, it is undefined
. That's because of the way hoisting works with var variables. You can check that very quickly by logging numberOfProducts above the if block.
Knowing that undefined
is a falsy value, the condition will be verified and the deleteShoppingCart
function will be executed.
This is just a fictional tiny example, but in a large code base with thousands of lines of code and without best practices, something like this can happen, and it's going to be a bug that will be hard to find.
So what are these best practices? What is the conclusion of all this? As a first step, as I told you many times, don't use var
to declare variables. Use const
most of the time to declare variables, and let
if you really need to change the variable later.
Also, to write clean code, you should declare your variables at the top of each scope. That will make your code at least look a little bit better.
Finally, always declare all your functions first and use them only after the declaration. This applies to all types of functions, even function declarations, which are hoisted. Before declaring them, you could use function declarations, but don't do that. It's just not clean.
To finish, since we're talking about the differences between let
, const
and var
, let's see another small difference between them.
Let's start by declaring one variable for each:
var x = 3;
let y = 10;
const z = 24;
Let's now look at the window object in the browser's console. The window object is the global object of JavaScript in the browser. You can see all kinds of stuff in the window object, like the alert window that we have used before or some other functions that we probably might have used before.
var x = 3;
let y = 10;
const z = 24;
console.log(window);
I'm not going to go into all these functions here, but you can, of course, explore this by yourself if you're interested. What matters here is that besides all these functions, we also get the property x
with a value 3
.
That's exactly the variable that we declared above using the var
keyword. However, we cannot find y
or z
anywhere in this object. That's because they were declared with let
or const
. This shows us that variables declared that way (with let
or const
) do not create properties on the window object.
To demonstrate this to you even better, we can write this:
console.log(x === window.x);
console.log(y === window.y);
console.log(z === window.z);
In conclusion, I just wanted to let you know that variables declared with var
will create a property on the global window object, which can have some implications in some cases. Again, you can take some time to explore the window object because it's quite interesting to see everything that is in there.
With that being said, the next lecture will be about the this keyword.