For the first 100 days of 2021, as part of #100DaysOfCode, I am re-introducing myself to common JavaScript concepts and blogging about them! For this post, I am going to talk about closures and IIFEs and how important they can be in JavaScript programming.
Closures
In JavaScript, functions are considered to be "first-class" because, like other variables, they can be assigned as a value, passed as an argument and even returned from other functions! Because JS functions are regarded this way, each time they are created, they come with an ability to utilize variables from outside its scope. This ability is known as a closure.
Closures refer to an enclosed function's access to its surrounding state, the lexical scope. For example, in this short code snippet:
let A = "Hello, World!"
function greeting() {
console.log(A)
}
greeting() // --> Hello, World!
Despite A
not being defined outside the scope of greeting()
, it is still accessible within the function. Let's see what happens if we change the value of A
below the definition of the greeting()
function:
let A = "Hello, World!"
function greeting() {
console.log(A)
}
A = "Hello, Other World!"
greeting() // --> Hello, Other World!
Normally, this isn't possible in other languages. But in JavaScript, thanks to closures, when greeting()
is executed, the A
variable within its scope is bound to the most up-to-date version of the global A
variable. Thus, "Hello, Other World!" is logged to the console.
But the most classic example of using JS closures is when you invoke a function inside another function. Let's look at another example:
function FuncA() {
let name = "Brandon"
function FuncB() {
console.log(name)
}
FuncB()
}
FuncA() // --> Brandon
Again, closures make this code functional (pun intended) by allowing enclosed inner functions to have access to everything inside its surrounding environment.
If you were to return FuncB
instead of just invoking it, the name
information is still there because of closures. We can execute either with additional variable assignment:
function FuncA() {
let name = "Brandon"
function FuncB() {
console.log(name)
}
return FuncB
}
let newFunc = FuncA()
newFunc() // --> Brandon
...or double parentheses:
function FuncA() {
let name = "Brandon"
function FuncB() {
console.log(name)
}
return FuncB
}
FuncA()() // --> Brandon
Since we're getting into more parentheses...
IIFEs
IIFE (or "iffy") stands for immediately invoked function expression. When you run JavaScript code, function statements go through a "creation" phase where they're prepared for execution but aren't run yet. This happens in the "execution" phase. But sometimes, we don't want a function to hang around and wait to be executed; we want it to run right as soon as it's created. Enter IIFEs.
(function(name) {
console.log("Hello, " + name)
}("Brandon")) // --> Brandon
In the example above, we enclose an anonymous function statement in an additional set of parentheses. If we didn't do this, we would a SyntaxError
thrown at us because function statement usually are expected to have a name. IIFEs get around that. IIFEs turn the function statement into an expression that is immediately invoked.
const AddTheSum = (function(a,b) {
console.log(a + b)
}(2,2))
// immediately invoked, no need to write AddTheSum to log to console
const Multiply = function(a,b) {
console.log(a * b)
}
Multiply(2,3) // function expression stored but not executed; you must use Multiply() to invoke.
Conclusion
If you code with JavaScript, you've already been using closers. Even if one function
is written in a file, it is still enclosed in the global scope. Closures let you link data defined in the global scope with the inner function that operate on that data.
There are many libraries and frameworks that wrap their entire code base in a function to avoid value collision in the global scope (IIFEs). This ensures that any duplicate variables with differing values belong in their respective contexts upon executing, preventing a collision.
Together, closures and IIFEs protect your JavaScript program and its data.