"Writing effective, error-free code requires an understanding of JavaScript's special function and variable hoisting behavior. This article delves into the details of hoisting, providing best practices and illustrative examples to enhance developers' proficiency. By mastering hoisting, developers can streamline their coding process and create more robust applications."
What is hoisting?
Hoisting is javaScripts default behaviour where variable and function declarations are moved to the top of their scope before the execution of code.
The JavaScript engine will normally execute code from the top to the button, line by line or one line after the other, and when it does, hoisting happens before execution during the semantic analysis phase. Variable and function declarations are moved to the top of their scope before the execution process. You can confirm this by using console.log()
or document.write()
to log out a function or variable before the code itself. Let us look at the illustrations below to shed light on the explanation above.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function square(a, b) {
document.write(a * b);
}
square(3,4);
</script>
</body>
</html>
This code will simply output 12 just as we can see below is the result of the code
Now, you can also call or invoke the function before the function is created, and after it is created, it will still give the same result. Are you surprised? This is only possible because of hoising; now let's see that. You can also try this on your own.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
square(3,4);
function square(a, b) {
document.write(a * b);
}
</script>
</body>
</html>
This will still give the same result as above; it will output 12 in the document page when you use document.write()
or in the console when you use console.log()
, just like the example above.
This is possible because of hoisting. So hoisting moves declaration
only and initialization
or assignment
are left in place. That means javascript
initializations
are not hoisted.
Types of Hoisting.
In this article we will be looking at two types of hoisting, which include:
- Variable Hoisting
- Function Hoisting
- Function Expression Hoisting
What is variable hoisting?
This can simply refer to the behaviour of javaScript to move a variable declaration to the top of its scope, regardless of its actual position in the code.
Just as I have pointed out while explaining hoisting using a function
as an example, variable declarations are also moved the same way the function is moved to the top, but in the case of variables, the output or result might seem a little bit different and not as direct as the case of function code that can be invoked or called before the actual code and it initializes
. In the case of variable hoisting, different results for different types of variables. Let's check this out! In the example above, we were able to call or invoke a function before the actual code, and we saw the output: let's use the same method using any of the types of variables (let
, const
, and var
). Let's see in the example below
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let name = "abudog";
const goat = "constano";
var dog = "vanetino";
console.log(name);
console.log(goat);
console.log(dog);
</script>
</body>
</html>
The code above worked without error, below is the result of the code.
But let's see what happens if we want to console.log()
our variable declaration before the code or before we write the actual code. For example, let's say we have var
, let
, and const
separated. Now let's see the different results that we will get.
Note: just write your code along for this will aid fast understanding of the subject matter.
<script>
// we are looking at `let` variable keyword first
// to see what will be log out in the console
console.log(name);
let name = "abudog";
// console.log(dog);
// var dog = "vanetino";
// console.log(goat);
// const goat = "constano";
</script>
Using the same principle as we have used in the code with function declaration, just like calling the variable
before actual code. See the output below; you can as well try this on your own
The result is uncaught referenceError: cannot access ‘name’ before initialization
Let's also see for const
variable what will happen
<script>
// We are looking at using `const` variable keyword now
// to see what will be logged out in the console
console.log(goat);
const goat = "constano";
// console.log(name);
// let name = "abudog";
// console.log(dog);
// var dog = "vanetino";
</script>
And this is the output from the console
.
Now we try var
variable keyword and let's see the result
<script>
// Now we are using `var` variable keyword
// to see what will be logged out in the console
console.log(dog);
var dog = "vanetino";
// console.log(goat);
// const goat = "constano";
// let name = "abudog";
</script>
In the case of var
variable keyword, the result is undefine.
Now we can see that the output is different from the first example, where a function
keyword is used to declare the function. Variable hoisting is peculiar, and not only that, we saw that let
and const
variables output different results and var
variable output undefined
. Now let's see what happened. The let
and const
declaration is hoisted to the top of their scope, creating a variable
name
and goat
with an uninitialized
value
. Let
and const
variables are not initialized until they are declared; they are in a temporary dead zone from the start of the scope until the declaration accessing a variable
in the temporary dead zone, so this results in a referenceError
. When you try to log name
for let
and goat
for const
variable
, they are still in the TDZ, so javaScript
throws a referenceError
.
Why doesn't this happen with var?
Variables declared with var
are also hoisted, but they are initialized
with undefined
immediately. This behaviour is by default
for var
variable before its declaration. Just like we can see in the last example above.
Don't forget the hoisting processes occur behind the scenes, and it gave birth to the results we can see or make the process work out the way we saw them.
Function and Function Expression Hoisting
What is function hoisting?
Function hoisting is a javaScript behaviour where function declarations are moved to the top of their scope, regardless of where they’re actually defined. This allows functions to be called before they’re defined
. I refer to the first example at the beginning of this article.
<script>
function square(a, b) {
document.write(a * b);
}
square(3,4);
</script>
Function declaration vs function expression
Before diving into function expression hoisting, let’s quickly review the difference between function declarations and function expressions:
Function declaration: a function declaration defined using the keyword
, for example, function
add( a, b) {
return a + b; }
Function expression: a function defined as an expression, often assigned to a variable
; for example, const
add = function(a, b) { return a + b; };
Here is the important thing or part: function
expressions
are not hoisted in the same way as function declarations. Only the variable declaration is hoisted, not the assignment. Consider the example below
<script>
// this is a function declaration
function add(a,b) {
return a + b
}
// this is a function expression
const addConst = function(a,b) {
return a + b;
};
console.log(add(3,5) + " " + "output of function");
console.log(addConst(3,5) + " " + "output of function expression addConst");
</script>
There are two functions here: function declaration
with the keyword function
and function expression
declared using the keyword const
, these two should log out the same result. This is the result below
Now let's see what happens if we try to console.log()
the two codes: the one with function declaration
and the other with function expression
. Using the same code.
<script>
// This is a function
console.log(add(3,5) + " " + "output of function");
function add(a,b) {
return a + b
}
// This is a function expression
console.log(addConst(3,5) + " " + "output of function expression addConst");
const addConst = function(a,b) {
return a + b;
};
</script>
Below is the output of the code
Now, the first code with function declaration
worked very fine, and the second code with function expression
using the const
keyword to declare it displayed an uncaught ReferenceError: cannot access addConst’ before initialization
. This is because only the variable declaration
is hoisted in variables defined
using const
and let
and not the assignment
. So you can't call the function
for function expression
before it’s assigned
to the variable. It is only possible when writing code with a function
keyword.
Best practices for avoiding Hoisting issues
-
Declare variables at the top of their scope:
Below are very good examples of
declaring
variable
orfunction
at the top of their scope before usage
<script>
// Hoisting with function declaration
function salute() {
console.log("Hello, Emmanue!");
}
// Hoisting with var
var name;
function meet() {
// The variable `name` is hoisted, but it's undefined here until it is assigned a value
console.log("Hello, " + name);
}
// Calling functions before they are defined
meet(); // Output: Hello, world!
salute(); // Output: Hello, undefined
// Assigning a value to the variable `name`
name = "Emmanuel";
// Calling the function again after assigning a value
meet(); // Output: Hello, Emmanuel
</script>
-
Use Let and Const for block scope:
Always use
let
andconst
variable for block scope Example: in javaScript,let
andconst
are used forblock-scoped
variable declarations, which means they are limited to theblock
in which they are defined either within aloop
,condition
, or afunction
In the code below we can see how to use this
<script>
function blockScopeExample() {
if (true) {
let blockScopedVariable = "I am block scoped!";
const blockScopedConstant = "I am a constant block scoped!";
console.log(blockScopedVariable); // Output: I am block scoped!
console.log(blockScopedConstant); // Output: I am a constant block scoped!
}
// Trying to access blockScopedVariable and blockScopedConstant here will result in a ReferenceError
// console.log(blockScopedVariable); // Uncaught ReferenceError: blockScopedVariable is not defined
// console.log(blockScopedConstant); // Uncaught ReferenceError: blockScopedConstant is not defined
}
blockScopeExample();
// Example with a loop
for (let i = 0; i < 3; i++) {
console.log(`Inside loop: ${i}`); // Output: Inside loop: 0, Inside loop: 1, Inside loop: 2
}
// Trying to access `i` here will also result in a ReferenceError
// console.log(i); // Uncaught ReferenceError: i is not defined
</script>
- Avoid re-defining variables in the same scope:
- Minimize global variables:
Conclusion
Mastering JavaScript function and variable hoisting is crucial for any developer aiming to write clean, efficient, and error-free code. By understanding how hoisting works, particularly with var, let, and const, you can avoid common pitfalls related to variable scope and initialization.
Adopting best practices such as declaring variables at the top of their scope, using let and const for block scope, and favoring function declarations where appropriate will not only enhance the readability of your code but also prevent unexpected behaviors that can arise from hoisting.
As you continue to explore JavaScript, keep these principles in mind and apply them in your projects. With practice, you'll gain a deeper understanding of how hoisting impacts your code, enabling you to leverage this knowledge for more effective programming. Embrace these practices, and you'll be well on your way to becoming a proficient JavaScript developer, equipped to tackle complex challenges with confidence.