To understand the fundamentals better, let’s recap on what exactly hoisting means. Also, as a reminder, JS is an interpreted language which means that unlike compiled languages JS code runs line by line.
Consider the following example.
After overanalyzing the code sample above, there are several questions that jump up.
- How is the function being accessed before declaration on line 6?
- How did line 1 not throw an error since at this point no variable called
- Why is line 9 printing
notyetdeclaredhas already been declared in the global scope?
This process is self explanatory but doesn’t really explain the anomalies we
saw during the execution of the code sample. While the execution context keeps
track of the execution of the code, Lexical environment keeps track of the
mapping of identifiers to specific variables. Lexical environment basically is
code for example a function or a block of code like a
for loop. Whenever a
function is created, a reference to the lexical environment in which it was
created is passed in an internal property named
To resolve an identifier within a lexical environment, the JS engine checks
the current environment for a reference. If no reference is found, it moves on
to the outer environment by using
[[Environment]]. This goes on until either
the identifier is resolved or a ‘not defined’ error is thrown.
So to elaborate on the first phase: it works in two steps.
The current code is ‘scanned’ for function declarations. Function expressions and arrow functions are skipped. For every function that is discovered a new function is created and bound to the environment with the function’s name. If the identifier name already exists, its value is overwritten.
Then the current environment is scanned for variables. Variables defined with
varand placed outside other functions are found and an identifier is registered with its value initialized to
undefined. If an identifier exists, the value is left untouched.
const are block variables and have a slightly different
var. More on that in another article.
Now that you have a basic idea of what a lexical environment is, lets get back to our sample code and deal with the questions.
When the global context is set up, the environment is scanned and the
hoisting() function is attached to an identifier. Then in the next step, the
notyetdeclared is registered and its value is initialized to
undefined per the steps.
How is the function being accessed before declaration on line 6?
hoisting() function is already registered to an identifier in phase 1 and
when the JS code starts executing in the global context in phase 2, it looks up
the lexical environment for
hoisting and finds the function even before its
How did line 1 not throw an error since at this point no variable
notyetdeclared is registered to an identifier and initialized to
undefined in phase 1 and hence no error is thrown.
Why is line 9 printing
notyetdeclared has already been
declared in the global scope?
Now we are in the
hoisting environment. In phase 1,
registered and initialized to
undefined because in this lexical environment
notyetdeclared has not been registered yet. If line 12 did
not contain the
var keyword, this would have been a different story.
Hopefully it is clear now that hoisting in JS is a simplistic view and technically the functions and variables aren’t moved anywhere.