In this tutorial, you will learn how to understand and implement JavaScript hoisting and JavaScript closure.
Table of Contents
You can skip to a specific section of this tutorial using the table of contents below:
What is JavaScript Hoisting?
JavaScript hoisting is a concept that allows you to refer to JavaScript functions and variables before they have been created.
As you'll recall from our section on JavaScript functions, hoisting is applied to functions that are created with the function
keyword. Here is an example:
printHello();
function printHello(){
console.log('Hello');
}
Even though the function printHello
is being invoked prior to its declaration, the function still runs as intended. Hello
is printed to the browser console.
How does hoisting work? Before JavaScript code is executed, it is first compiled. Compilation is a series of steps taken by a programming language to make its statements interpretable by the computer.
During this compilation step, JavaScript moves all function and variable declarations to the start of the script. This causes hoisting!
Hoisting also works for variables. However, it only hoists the variable declarations, not the values assigned to the variable. If you attempt to reference a variable prior to its declaration in a JavaScript file, it will return the undefined
value.
Here is an example:
console.log(var1);
var var1 = "This is the value assigned to var1!";
This logs undefined
to the browser console.
Another important concept to understand with respect to variable hoisting is that it only works with the var
keyword. If you attempt to hoist a variable with the const
or let
keyword, your browser will return an error message.
Return to the Table of Contents
What is JavaScript Closure?
JavaScript closures are arguably one of the more difficult components of the JavaScript programming language. My hope is that I will be able to elucidate the closure concept for you in this tutorial.
A JavaScript closure is the ability to access a parent-level scope from a child scope even when the parent function has been terminated. Closures occur when one JavaScript function is nested inside of another.
Let's see JavaScript closures in action.
To start, let's create a function called innerFunction
that will act as the child function. The innerFunction
function will have a variable called innerVariable
.
function innerFunction(){
const innerVariable = "Inner variable";
}
Next, let's nest innerFunction
inside another function called outerFunction
. The outerFunction
function will have a variable called outerVariable
.
function outerFunction(){
const = outerVariable = "Outer variable";
}
Now let's nest innerFunction
inside of outerFunction
:
function outerFunction(){
const outerVariable = "Outer variable";
function innerFunction(){
const innerVariable = "Inner variable";
}
}
Next, let's add two console.log
statements inside of innerFunction
- one for the innerVariable
variable and one for the outerVariable
variable:
function outerFunction(){
const outerVariable = "Outer variable";
function innerFunction(){
const innerVariable = "Inner variable";
console.log(outerVariable);
console.log(innerVariable);
}
}
Finally, let's add a function call to innerFunction
within outerFunction
. This means that each time outerFunction
is invoked, it will also invoke innerFunction
:
function outerFunction(){
const outerVariable = "Outer variable";
function innerFunction(){
const innerVariable = "Inner variable";
console.log(outerVariable);
console.log(innerVariable);
}
innerFunction();
}
Executing the outerFunction
function will now cause the following output to be logged to the console:
Outer variable
Inner variable
So how do closures come into play here?
Well, a closure would occur when you do not call innerFunction
from inside of outerFunction
, but instead at a later point inside of your program.
To do this, we need to make two small changes to our JavaScript code.
First, we need to add a return
statement to outerFunction
that actually returns innerFunction
when outerFunction
is executed.
Second, we need to run outerFunction
and assign this to a new variable.
Here are these changes in action:
function outerFunction(){
const outerVariable = "Outer variable";
function innerFunction(){
const innerVariable = "Inner variable";
console.log(outerVariable);
console.log(innerVariable);
}
return innerFunction; //Note that there are no round brackets here
}
theInnerFunction = outerFunction()
theInnerFunction
has now been assigned the innerFunction
function from inside of outerFunction
.
What do you think happens when we attempt to run theInnerFunction
? After all, it will attempt to console.log
the outerVariable
variable, which should only be accesible within the outerFunction
function because of the scoping that we learned about in our last lesson.
If you run theInnerFunction
, the following output will be logged to the console:
Outer variable
Inner variable
Woah - so we have found a way for function-scoped variables to be accessed outside of the function! This is exactly what developers mean when they refer to a closure
.
You might be wondering why closures might be useful. After all, this basic example that we have just worked through seems unnecessarily complicated.
Well, closures can be used to create private variables
, which are an advanced concept that we will be learning more about later in this course.
If closures still seem complicated to you, do not worry. We will be referring to them many more times throughout the rest of this course.
Return to the Table of Contents
Final Thoughts
In this tutorial, you learned about JavaScript hoisting and closure. Specifically, we discussed how hoisting allows you to refer to JavaScript variables and functions prior to their declaration, and how closures allow you to access function-scoped variables outside of that specific function.