var, hoisting, and temporary deadzones (js)

ref –

Hoisting is about a variable or function definition (that’s being used or initialized) being available beforehand.

For example, if we declare and initialize a variable to a string, that variable is actually available beforehand at the very top. This is because the interpreter will get all the variables, and declare them at the very top first.

global, local scope variable over-rides

Function definition declaration

Function definition declarations are hoisted.

function definition hoisting only occurs for function declarations. In other words,
when you declare the function definition, then you can simply call it.

However, when the function definition is part of a function expression, then hoisting will not work.

For example, when we have a function expression variable that takes on the function definition, we cannot hoist the function expression variable.

Function definitions as part of function expression are not hoisted

If you have a function definition, and its part of a function expression, then you CANNOT do hoisting:

Class declarations ARE NOT hoisted

class declarations are not hoisted like function declarations.

For example, if you place the following code above the Animal class declaration section, you will get a ReferenceError:


var is function scoped. It is available in the whole function even before being declared

Declarations are hoisted. So you can use a variable before it has been declared.
Initializations are NOT hoisted. If you are using var, ALWAYS declare your variables at the top.

We declare x globally.

There is an if block, and in it, a var is declared with the same name.
that name gets hoisted to the top of the function. The name is the same
as the global one (x), and thus over-rides it.


translates to:

In es6, when we use let, it works as expected:

let will hoist the variable to the top of the BLOCK (NOT top function like var)

Referencing the variable in the block before the variable declaration results in a ReferenceError

Temporal Dead Zone

“Temporal Dead Zone” is the zone from the start of the block until the variable is declared

Can you guess what console.log(x) will print now? Well, actually, the answer is nothing — the code above will throw a ReferenceError due to the TDZ semantics. That is because let/const declarations do hoist, but they throw errors when accessed before being initialized (instead of returning undefined as var would).

let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment.

In other words, the variables are created when their containing Lexical Environment is instantiated. This means whenever control flow enters a new scope (e.g. module, function or block scope), all the let/const bindings belonging to the given scope are instantiated before any code inside of the given scope is executed — in other words,

let/const declarations DO HOIST

…but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

This is the Temporary Dead Zone

A given let/const-declared binding can’t be accessed in any way (read/write) until control flow has evaluated the declaration statement — that does not refer to the hoisting, but rather to where the declaration actually is in the code.

And the last part:

If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

This simply means that:

Is equivalent to:

Trying to access x in any way before control flow evaluates the initializer (or the “implicit” = undefined initializer) will result in a ReferenceError, while accessing it after the control flow has evaluated the declaration will work fine. i.e reading the x variable after the let x declaration in both samples above would return undefined.

Does the code execute without errors? What is the value of x after the code executes?

First off, remember that a let/const variable only counts as initialized after its initializer has been fully evaluated — that is, after the assignment’s right-hand side expression has been evaluated and its resulting value has been assigned to the declared variable.

In this case, the right-hand side expression tries to read the x variable, but x’s initializer has not been fully evaluated yet.

Thus, trying to read x at 2) results in a TDZ, and thus will throw a ReferenceError.

— in fact we are evaluating it at that point — so x still counts as uninitialized at that point and thus trying to read it throws a TDZ ReferenceError.

Other Examples

In the first line, the f() call makes control flow jump to and execute the f function, which in turn tries to read the b constant which, at this point in the runtime, is still uninitialized (in TDZ) and thus throws a ReferenceError. As you can see, TDZ semantics apply when trying to access variables from parent scopes as well.

Here, we have parameters a and b defined in the IIFE. Parameters are evaluated from left to right. At the left, we have a = 1.
Then the next parameter b takes on a, which at that time, evaluates to 1, thus b = 1.

The a, b are parameters. They are bound to the IIFE. thus, parameter b’s scope is in the IIFE. b gets hoisted. When a tries to assign b, it will throw a ReferenceError. Because b does not have a value yet.