One of the trickier aspects of JavaScript for new JavaScript developers is the fact that variables and functions are "hoisted." Rather than being available after their declaration, they might actually be available beforehand. How does that work? Let's take a look at variable hoisting first.
// ReferenceError: noSuchVariable is not defined
console.log(noSuchVariable);
βThe JavaScript Interpreter
When you execute your JavaScript code, the interpreter goes through the code twice.
The first run through the code is where it does a safety check and small optimizations of your code. Safety checks such as making sure that the syntax is right, if there are any calls to eval or with, etc. Then, it optimizes the code as best as it can to ensure better performance when it is executed. This is also where hoisting occurs (more on this soon). This is also referred to as the compile run.
The second run is where it actually executes your code by going through it line by line, doing the assignments, calling the functions, and so on.
β What is Hoisting?
π Hoisting is when the JavaScript interpreter moves all variable and function declarations to the top of the current scope. It's important to keep in mind that only the actual declarations are hoisted, and that assignments are left where they are.
π Hoisting is done during the interpreter's first run through the code.
Variable Declarations
Let's start with a basic example and look at the following code:
'use strict';
console.log(book);
var bar = 'book';
console.log(book);
// output: undefined , book
At a first glance, you may think that the code would throw a ReferenceError
on (console.log(book);) because book
has not been declared
yet.
π However, with the magic of hoisting
, it won't throw a ReferenceError
but the value
of book
will be undefined
at that point. This is because the JavaScript interpreter does a first run through the whole code and declares all variables and functions at the top of the current scope, and then, on the second run, will execute the code.
- Here's what the same code would look like after the interpreter's first run:
'use strict';
var book;
console.log(book); // undefined
bar = 'book';
console.log(book); // 'book'
Notice how book
is now declared at the top (var book)
but is not yet assigned at that point? It's a subtle but important difference, and this is why book
is logged as undefined instead of throwing a ReferenceError
.
β Function Declarations
π Hoisting also applies to function declarations (not function expressions). Let's analyse the following sample code:
'use strict';
read();
function read() {
console.log(book);
var book = 'book';
}
console.log(book);
In this sample code, we are able to successfully call the function read
since it's a function declaration and therefore it ishoisted
as-is to the top of the current scope
.
π Then, read
will output undefined when calling it since, as in the previous example, book is hoisted to the top of its current scope
, which is function read()
. This means that book
was declared before calling console.log(book)
but it has not yet been assigned a value (book = 'book')
.
π π The important thing to note here is that book
was hoisted at the top of its current scope
.
- This means that it was not declared in the global scope but in the function's scope instead.
'use strict';
function read() {
var book;
console.log(book); // undefined
book = 'book';
}
read();
console.log(book); // ReferenceError: book is not defined
π β Pay attention ββ
- How
read()
was moved to the top, andbook
is declared inread()
? This means that, when you callconsole.log(book)
, it will not find the variable book in thegeneral scope
and will throw aReferenceError
.
β Function Expressions
the third use case I'd like to cover is how function expressions are not hoisted as opposed to function declarations. Instead, it's their variable declarations that are hoisted.
'use strict';
book();
var book = function () {
console.log(read);
var read = 'read';
}
// output: Uncaught TypeError: book is not a function
This code throws a TypeError: book is not a function error since only the variable declaration var book
is hoisted to the top of the file, and the assignment of the function to book is done on the interpreter's second run only.
- Here's what the same code would look like after the interpreter's first run:
'use strict';
var book;
book(); // `book` has not been assigned the function yet
book = function () {
console.log(read);
var read = 'read';
}
What Takes Precedence?
the last use case I'd like to cover is that function declarations are hoisted before variables. Let's have a look at the following code:
'use strict';
console.log(typeof book);
var book = 'book';
function book () {
var read = 'read';
console.log(read);
}
//output: function
In this example, typeof book
returns function
instead of string
, even though the function book()
is declared after the variable
. This is because function declarations are hoisted before variable declarations, so book = 'book'
is executed on the second run, after calling typeof book
.
On the first run, the interpreter will hoist book()
at the top of the current scope, and then will get to the var book = 'book' line. At that point, it realises that book
was already declared so it doesn't need to do anything and will continue its first run through the code.
Then, on the second run (which basically executes the code), it'll call typeof book
before it gets to the assignment
book = 'book'
.
Here's what the same code would look like after the interpreter's first run:
'use strict';
function book () {
var read = 'read';
console.log(read);
}
console.log(typeof book); // 'function'
book = 'book';
π We use var
to declare a variable, and we have initialized it with a value. After declaring and initializing, we can access or reassign the variable.
π If we attempt to use a variable
before it has been declared and initialized, it will return undefined
.
// Attempt to use a variable before declaring it
console.log(x);
// Variable assignment
var x = 100;
//Output: undefined
π However, if we omit the var keyword, we are no longer declaring the variable, only initializing it. It will return a ReferenceError and halt the execution of the script.
// Attempt to use a variable before declaring it
console.log(x);
// Variable assignment without var
x = 100;
//Output: ReferenceError: x is not defined
π β The reason for this is due to hoisting, a behavior of JavaScript in which variable and function declarations are moved to the top of their scope. Since only the actual declaration is hoisted, not the initialization, the value
in the first example returns undefined
.
To demonstrate this concept more clearly, below is the code we wrote and how JavaScript actually interpreted it.
// The code we wrote
console.log(x);
var x = 100;
// How JavaScript interpreted it
var x;
console.log(x);
x = 100;
π JavaScript saved x
to memory as a variable before the execution of the script. Since it was still called before it was defined, the result is undefined and not 100. However, it does not cause a ReferenceError and halt the script. Although the var keyword did not actually change location of the var, this is a helpful representation of how hoisting works. This behavior can cause issues, though, because the programmer who wrote this code likely expects the output of x
to be true
, when it is instead undefined
.
We can also see how hoisting can lead to unpredictable results in the next example:
// Initialize x in the global scope
var x = 100;
function hoist() {
// A condition that should not affect the outcome of the code
if (false) {
var x = 200;
}
console.log(x);
}
hoist();
// Output: undefined
π In this example, we declared x
to be 100
globally. Depending on an if statement
, x
could change to 200
, but since the condition was false it should not have affected the value of x
. Instead, x
was hoisted to the top of the hoist()
function, and the value
became undefined.
π This type of unpredictable behavior can potentially cause bugs
in a program. Since let
and const
are block-scoped, they will not hoist in this manner, as seen below.
// Initialize x in the global scope
let x = true;
function hoist() {
// Initialize x in the function scope
if (3 === 4) {
let x = false;
}
console.log(x);
}
hoist();
// Output: true
π Duplicate declaration of variables, which is possible with var
, will throw an error with let
and const
.
// Attempt to overwrite a variable declared with var
var x = 1;
var x = 2;
console.log(x);
//Output: 2
// Attempt to overwrite a variable declared with let
let y = 1;
let y = 2;
console.log(y);
// Output: Uncaught SyntaxError: Identifier 'y' has already been declared
π To summarise, variables introduced with var
have the potential of being affected by hoisting, a mechanism in JavaScript in which variable declarations are saved to memory. This may result in undefined variables in oneβs code.
π β The introduction of let
and const
resolves this issue by throwing an error
when attempting to use a variable before declaring it or attempting to declare a variable more than once.
Top comments (0)