JavaScript Demystified: Unveiling the Code Wizardry - Hoisting
Tags: javascript, programming, beginners
Published: 2024-02-17 20:52:44

People commonly explain hoisting as **declarations of variables and functions being moved to the top of your code**. At the same time, this appears to be happening; it’s essential to understand precisely what is happening. Because that definition **is a Myth**, a convention created to discuss the idea of lexical environment without so much overhead.

Before the ECMAScript® 2015 Language Specification, "hoisting" was not used in any normative specification.


```js
foo("My Name");

function foo(name) {
  console.log(name);
}
```

In this case, the function was called before its definition. People often claim that the function is "lifted" to the beginning of the code, but it works due to function prototypes.

```js
function foo(name) {
  console.log(name);
}

foo("My Name");
```

But that's incorrect, there's no such thing as lifting functions and variables, you see, the code isn’t moving anywhere. It isn’t magically being moved to the top of the file. At the compiler phase, all functions and variable declarations are **Hoisted** at the Memory Heap, called the Lexical Environment. Then, the Engine will execute the code from top to bottom, adding every scope at the Execution Call Stack.

> _Hoisting_ refers to the default behaviour of Javascript to process and put all variables and function declarations into memory first during the compile phase of its execution context, regardless of where they are written in the code. _Maya Shavin_

> _Lexical Environment_ is a data structure with identifier-variable mapping on the memory heap.

![Code Compile and Executon](https://thepracticaldev.s3.amazonaws.com/i/rlfc71uocpudolg0ny8l.png)

Difficult? Too many concepts? let's make it more simple. A few seconds before your code is executed, the compiler will go through every line, collecting variables and function declarations and storing those in the memory to optimise and utilise even before his declaration in the source code. In other words, the lexical environment is a fancy way to say "local memory scope".

![Execution Context animated](https://thepracticaldev.s3.amazonaws.com/i/yhpzy8uvjp5ls966n181.gif)

Let's see if those concepts can be put into practice.

```javascript
say("Hello"); // Hello
console.log(world); // world is undefined

function say(word) {
  console.log(word);
}

var world = "World";
```

So, in this example, the compiler will run through the code and find the function `say` and save his reference into the memory, making it available to be called in the execution phase, returning `'Hello'`, but why the `world` variable is undefined?

> JavaScript only hoists variable declarations. 
> Initializations are not hoisted.

So the variable declaration `world` will be hoisted at the memory. In the execution phase, the variable is called, but since it isn't initialized yet, the returned value is `undefined`; here's another example.

```javascript
if (x === undefined) {
  console.log("x is not defined");
} else {
  console.log(x);
}

var x = "Defined";

//result "x is not defined"
```

Here the `x` variable is stored in memory during the compile phase but with the value of `undefined`, because the **assignment of value to a variable happens only in the execution phase**. 

So what happens is something like this before the **execution phase**
```javascript
var x;

if (x === undefined) {
  console.log("x is not defined");
} else {
  console.log(x);
}

x = "Defined";

//result "x is not defined"
```

Let's see what happens if we swap the declarations of the variable 'x'

```javascript
var x = "Defined";

if (x === undefined) {
  console.log("x is not defined");
} else {
  console.log(x);
}

//result "Defined"
```

This time, the variable `x` will be hoisted/lifted by the compiler with the value of `undefined`, but when the execution phase starts, the `x` variable will be evaluated at the first line with the value of `Defined` making possible to run the else condition.

There's also a lot of confusion about undefined and undeclared variables (Reference Error). When a variable is undefined, it means that the variable has been declared at the compiler phase but not initialized, and undeclared are variables that weren't declared in the lexical environment or the JS Engine doesn't find an available reference for them.

## What happens with ES6 syntax?

So, what happens with all the features of ES6, like `let` and `const`? Are they hoisted? let's see

```javascript
console.log(x);
//ReferenceError: x is not defined

let x = "Hello";
```

**All declarations in JavaScript function, var, let, const, even classes, are hoisted at the compiler phase**, but while the var declarations are initialized with undefined, `let` and `const` declarations remain uninitialized, so where it's at the first line of the example? he was in a place that we call "**Temporal Dead Zone**".

![TDZ](https://media1.giphy.com/media/vwT1bQ8zojmWQFx297/giphy.gif?cid=790b76116e2deb8ea24a2671b46cc66742667737a619f163&rid=giphy.gif)

No, nothing like that, let's see what ECMAScript 2015 spec tells us about _TDZ_.

> **NOTE 13.2.1** Let and const declarations define variables scoped to the running execution context’s Lexical Environment. The variables are created when their containing Lexical Environment is instantiated **but may not be accessed in any way until the variable’s LexicalBinding is evaluated**. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer, the variable is assigned the value undefined when the LexicalBinding is evaluated.

In other words, `let` and `const` are only initialized when their assignment of value is evaluated, and that happens during the execution phase, so in the example, the `x` variable will be evaluated only after called by `console.log`, that's why throws a ReferenceError.

Now, let's see what happens when we introduce the block scope.

```js
var name = "Guilherme";
displayName();
function displayName() {
  console.log(name);
  // ReferenceError meaning TDZ error
  let name = "Scotti";
}
```

Now, the global scope has a `name` variable with the value of 'Guilherme', but when the Engine enters the block scope, it creates another execution context and hoists the variable `name` into the TDZ of this block context. So, when the `console.log` runs, the variable `name` is unreachable.

To summarise, TDZ means don't touch that until it is initialized.

![don't touch that](https://media1.giphy.com/media/CvsQzv9hZe2Ry/giphy.gif?cid=790b7611cd36279a241394a3cc080910cf7b857e101a6a6a&rid=giphy.gif)

The whole point of the TDZ is to make it easier to catch errors where accessing a variable before it’s declared in user code leads to unexpected behaviour. This happened a lot with ES5 due to hoisting and poor coding conventions. In ES6 it’s easier to avoid.

## Function Expressions or Declarations

There are three ways to define a function in JavaScript: a function declaration (assigning the function value into a variable), function expression, and ES6 Arrow Functions. So, are they all Hosted equally?

```javascript
hello(); // "Hello"
world(); // TypeError: world is not a function
exclamation();
// ReferenceError: exclamation is not defined

function hello() {
  console.log("Hello");
}
var world = function() {
  console.log("World");
};
const exclamation = () => console.log("!");
```

We already know that Function Declarations are Hoisted during the compile phase, so the `hello()` function is ready to be called at line 1 because it's a function declaration.

At line 2, we call the function `world()`, but actually, we are calling the variable `world` that was hoisted with the same rule as any other variable with the default value of `undefined`. Therefore, when the execution reached line 2 and tried to call `world()`, it failed returning a `TypeError`, because undefined is not a function!

That also happens with the arrow function, but with a little difference: ES6 Syntax is initialized only at value evaluation on the execution phase, so there's the Reference error because of the **const** declaration.

## Conclusion

This is an important foundation in the understanding of how JavaScript works under the hood, and the main conclusion that can be drawn is that **all declarations in JavaScript function, var, let, const, even classes, are hoisted at the compiler phase, but with different assignments of value**. Variables are hoisted with the value of `undefined`, except `const` and `let`, which are only initialized when their assignment of value is evaluated, and that happens during the execution phase; before that, they are in **TDZ**.

We also saw that function declarations are hoisted and evaluated normally, but function expressions are treated precisely like variables. I hope you guys can glimpse some functionalities that run on our daily code in JS.

Now that you know more about Hoisting, it’s time to move to dive Deeper with [Javascript Demystified Series](https://dev.to/scottibr/javascript-demystified-unveiling-the-code-wizardry-from-compilation-to-execution-dcf).


If you want to dive deeper into some concepts, visit the [References](https://github.com/ScottiBR/my-articles/blob/master/JavaScript/Hoisting/References.md). Please post any feedback, questions, or requests for topics. I would also appreciate 👏 if you like the post so that others can find this too.