const - JS | lectureHow JavaScript Works Behind the Scenes

Hoisting and TDZ in Practice

How JavaScript Works Behind the Scenes

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.

script.js
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.

script.js
console.log(me);
console.log(job);
console.log(year);

var me = 'John';
let job = 'Software Engineer';
const year = 1993;

Log Output (me and job)

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.

script.js
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.

script.js
// 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.

script.js
console.log(addFuncDec(3, 3));

// Function Declaration
function addFuncDec(a, b) {
  return a + b;
}

// JS Code

Log Output addFuncDec

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.

script.js
console.log(addFuncDec(3, 3));
console.log(addFuncExp(3, 3));console.log(addArrowFunc(3, 3));
// JS Code

Log Output addFuncExp

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.

Log Output VAR addFuncExp

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:

script.js
addFuncExp(3, 3);

We are trying to call undefined basically. That is, we are doing something like this:

script.js
undefined(3, 3);

Which if we try to run in the console, we will get a similar error saying undefined is not a function.

Log Output calling undefined

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.

script.js
console.log(addFuncDec(3, 3));
console.log(addFuncExp);console.log(addFuncExp(3, 3));
console.log(addArrowFunc(3, 3));

// JS Code

Log Output value of addFuncExp

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.

script.js
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.

script.js
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:

script.js
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! 😳

Log Output deleteShoppingCart

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;

script.js
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:

script.js
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.

script.js
var x = 3;
let y = 10;
const z = 24;

console.log(window);

Log Output window object

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:

script.js
console.log(x === window.x);
console.log(y === window.y);
console.log(z === window.z);

Log Output x, y and 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.