An immediately invoked function expression - often abbreviated to IIFE and pronounced 'iffy' - is a very powerful and useful construct in JavaScript.
You've probably already came across something that looks like this:
(() => {
console.log('Hello, world');
})();
A function that is defined and then immediately executed is a fairly simple concept, but how does it actually work? Why would you use it? Whats with all of the parentheses?
Let's go through a few basic concepts and understand how the JS engine parses this code block.
Statements vs. Expressions
First, it is important we understand the difference between a statement and an expression.
A statement is simply a stand alone unit of execution which does some type of work. A simple example is the if
statement, which will execute the code defined in its block if a condition is true
.
In contrast, an expression is a unit of execution which can also perform some type of work but will then be reduced (or evaluated) to a value. For example the expression 3 > 2
will compare the two values, and then be evaluated to the value true
.
This is important because in JS, a function can be either a statement or an expression. A function statement looks like this:
function greet() {
console.log('Hello, world');
}
When the JS engine interprets this snippet, it will allocate space in memory for this function and create a name - greet
- which points to said space in memory. One can then execute, or invoke, this function by appending the name with a pair of parenthesis: greet()
.
On the other hand, a function expression looks like this:
var greet = function() {
console.log('Hello, world');
}
Here, the JS engine will first allocate a space in memory for the variable greet
and set it to undefined
. Then, at the moment it evaluates the function expression, it will create an object and set it up with the code inside of the function body. Finally, it will evaluate the assignment expression using both operands of the assignment operator (=
) - that is the object and the variable. The assignment expression results in the greet
variable pointing to the address in memory of the previously created object.
You might be confused by the use of the word "object" here. Indeed, in JS a function is actually a special type of object. Function objects have a special property called [[Code]]
which holds the actual body of the function. When we append a pair of parenthesis to a name as in greet()
the engine simply looks for the property and executes it.
Immediate Function Execution
Executing a function expression doesn't have to take place after its assignment to a variable. We can append a pair of parenthesis right after the expression which evaluates to it and execute it right then and there:
var greet = function() {
console.log('Hello, world');
}();
This is indeed an IIFE. If you execute this snippet in a browser, you will see that the log happens right away. However, inspecting the variable greet
will tell you that it is set to undefined
. Shouldn't it hold the function object?
To understand why that is, let's look at the operator precedence table on MDN.
This table describes the order in which operators will be evaluated. Operators with a higher number in the 'precedence' column will be evaluated before the ones with a lower number.
If we look for the precedence value of the assignment operator (=
) we will find that it is 2. And if we look for the value of the function call operator (...()
) we will see that it is 18, a much higher number.
Therefore, the order in which the engine will interpret the snippet is:
1) Allocate an address in memory with the name greet
and set it to undefined
.
2) Evaluate the function expression to create a function object.
3) Execute the function. Since the function has no explicit return value, this expression evaluates to undefined
.
4) Finally assign the result of the last evaluation to the variable greet
.
Naturally, if the function had an explicit return value we would have ended up assigning that to the variable:
var greet = function() {
return 'Hello, world';
}();
This is in fact an IIFE, just not its most popular form. What you probably want to do is have a stand alone function expression and then execute it.
Stand Alone Expressions
The JS engine does not mind interpreting useless expressions which never get used anywhere:
3; // number expression
"I'm a string" // string expression
{ name: 'object literal' } // object literal expression
All of the above expressions will be evaluated into their literal values and then immediately thrown away. If you execute the above in a browser you will see that no error is thrown. However, if you try to do the same for a function expression:
function(name) {
console.log('Hello, ' + name);
}
The engine will throw a syntax error with the message of "Function statements require a function name. But wait, did
it just say "statement"?
Indeed, the difference between a function statement and a function expression is not whether or not we name the function after the keyword function
, but rather where it sits lexically in our code.
When we declare a function as an operand of the assignment operator =
, the engine knows to evaluate it as an expression. When we declare a function as a parameter of another function or object method, the engine also knows to
evaluate it as an expression. Finally, when we declare a function stand-alone, it will be interpreted by the engine as a statement and it will expect a name or throw an exception.
To actually create a stand alone function expression, we have to trick the syntax parser into interpreting it as an expression. There is actually a few different ways to do this, but the most popular way is to wrap the function in parentheses:
(function(name) {
console.log('Hello, ' + name);
});
The outer set of parenthesis here are the 'grouping operator'. If you refer to the MDN documentation on them you will learn it controls the precedence of evaluation in expressions. As in, it expects everything contained withing it to be an expression. Thus, we've created a stand alone function expression.
Once again, we can execute the expression immediately after its evaluation into an object by appending it with another set of parenthesis:
(function(name) {
console.log('Hello, ' + name);
})();
This time, the parenthesis are the "function call" operator. Thus, a pair of parenthesis can be three different things depending on where it sits lexically in our code: the grouping operator if its stand alone, the function call operator if it is appended to a name or an argument list when its after the function
keyword.
With the introduction of arrow functions in ES6, the most popular way of declaring an IIFE is with them:
((name) => {
console.log('Hello, ' + name);
})();
An arrow function is simply a function expression with no binds for the this
or super
keywords. A topic for another post.
Why Though?
How are IIFEs useful? There are a few different uses for them. There first one is as a bypass to the rule that you can't use async/await in the top level of your code:
(async () => {
// fetch some data or do some other async thing here
})();
The second one is to avoid polluting the global namespace with variables which we will only use once during the initial phase of our script:
(() => {
// some initiation code
let firstVariable;
let secondVariable;
})();
// firstVariable and secondVariable will be discarded after the function is executed.
Conclusion
That is all. I hope you've learned as much about IIFEs reading this post as I did writing it!
Top comments (0)