What is an IIFE?
Armed with an understanding of the difference between Function Declarations and Function Expressions, I want to narrow the aperture and try to answer the question, What is an IIFE?
An IIFE, or Immediately Invoked Function Expression, is best recognized as a pattern for writing a function that runs itself as soon as it is declared.
Consider the following code, which can be put into a set of <script></script>
tags within an HTML file and run within a browser:
function sum() { return 10 + 20;}sum();// expected result: 30
This code doesn't do much. I have declared a function sum()
that returns the sum of 10
and 20
. Below the function declaration, I call the function, allowing it to run.
This pattern is fairly straightforward, but it can be improved. For one thing, if I were to console.log(sum)
after the function declaration, I would see that the sum()
function exists globally within the window
. This would be an issue if I only wanted to use this function once and never again, as it would be taking up global memory or polluting the global scope. Another issue here is that we have the function declaration and the function call broken out into two steps. If all we want to do is define some functionality and run it immediately, we can accomplish that using a different pattern:
(function () { return 10 + 20;})();// expected result: 30
In this second example, I have rewritten the sum()
function as an anonymous function expression wrapped in a Grouping Operator, which is a pair of parentheses ()
. It is then followed immediately by another pair of parentheses ()
which calls the function expression, causing the anonymous function to run. When this function runs, it returns the sum of 10
and 20
. If you put this code into your HTML file and open it in the browser, this code runs immediately. This is an example of an Immediately Invoked Function Expression.
You'll recall that a Function Expression is any valid piece of JavaScript that returns a function. The combination of the Grouping Operator and the parentheses that invokes the function inside of the Grouping Operator, ( {/* function to be returned goes here */})()
is the code that will return a function. The function that is actually written inside of the Grouping Operator is the function that gets returned. But this pattern tells that function to go ahead and run.
IIFE Grammar, Syntax, and Variations
There are three main ways to write an IIFE:
- As a classic function expression
- As an arrow function expression
- As an async function expression
But there are also a number of alternative ways of writing and enforcing IIFEs. I'll cover as many as I can within this section.
Classic Function Expression IIFE
(function () { /* functionality goes here */})();
We have seen this pattern already in our first IIFE example. We simply pass a function expression (using the function
keyword) to the Grouping Operator.
Arrow Function Expression IIFE
(() => { /* functionality goes here */})();
In this pattern we replace the classic function expression with an arrow function expression. Bear in mind here that arrow function expressions have some limitations – they are always anonymous and carry certain restrictions, notably around the usage of the this
keyword.
Async Function Expression IIFE
// as a classic function expression(async function () { /* functionality goes here */})();
// or, as an arrow function expression(async () => { /* functionality goes here */})();
With this pattern, we simply prepend the function expression within the Grouping Operator with the keyword async
. This works for both classic and arrow function expressions. Typically there will be an await
statement within the function block. I will cover the benefits of this pattern later on in this post.
Named IIFEs
The IIFEs we've looked at so far have all been anonymous function expressions, but named function expressions can also be used:
(function myCoolIffe() { /* functionality goes here */})();
As with regular function expressions, arrow functions cannot be given names and are always anonymous.
Passing Arguments to the IIFE
If your IIFE needs some data passed as an argument, you simply name the parameter(s) in the function expression and then pass the argument(s) within the second set of parentheses:
(function (val1, val2) { return val1 + val2;})(10, 20);// expected result: 30
Other IIFE Syntax Variations
We've observed some of the most common syntactic approaches to writing IIFEs, but there are a few other methods that are less common to be aware of. You may find these patterns lurking in projects you inherit that someone else wrote.
Parentheses Placement
The second set of parentheses can sometimes be found within the first set of parentheses:
(function () { /* functionality goes here */})();
Operators
Rather than an enclosing set of parentheses, the !
, ~
, -
, +
, or void
operators can prepend the function expression and enforce it:
!(function () { /* functionality goes here */})();
~(function () { /* functionality goes here */})();
-(function () { /* functionality goes here */})();
+(function () { /* functionality goes here */})();
void (function () { /* functionality goes here */})();
Expected Expressions
In any place where an expression is already expected, the enclosing set of parentheses is not necessary:
// within variable definitionlet myCoolVariable = (function () { /* functionality goes here */})();
// within a logical AND operator statementtrue && (function () { /* functionality goes here */ })();
// within a comma operator statement0, (function () { /* functionality goes here */ })();
Defensive Semicolon
In some cases you may find a semicolon ;
right before an IIFE. You may recall that in most cases semicolons are optional in JavaScript, as the compiler will handle automatic semicolon insertion for you. Some prettier or linter rules will even remove semicolons when they run. However, automatic semicolon insertion and the opening parenthesis usually found with an IIFE don't always get along:
let sum, val1, val2;sum = val1 + val2(function () { /* functionality goes here */ })();// expected result: TypeError: val2 is not a function
The compiler may parse this example as var2(function() { /* */})();
, which would be an issue. So as a workaround, developers may put a semicolon ;
directly before the IIFE, ensuring the code gets parsed correctly:
let sum, val1, val2;sum = val1 + val2;(function () { /* functionality goes here */})();
When to Use an IIFE
We have seen a couple of the benefits of IIFEs in action already, but let's dig into those benefits more explicitly. You should reach for the IIFE pattern if you are trying to:
- Run some code immediately
- Avoid global namespace pollution
- Alias global variables
- Write code modules that avoid naming collisions and/or have private and public methods
- Use the Index within
for
Loops - Use
await
andfor-await
in older environments
Running Some Code Immediately
IIFEs excel when you want to initialize your application by running some code once and never worrying about it again.
Here's a trivial example:
(function sayHello() { console.log("Hi there!");})();
This IIFE, named sayHello()
, will run as soon as it is loaded into its browser or its environment. All we're doing here is greeting our user in the console, but maybe you have a tracking script you need to run as soon as possible, or you need to initialize some functionality on your website. In this case you could use an IIFE.
Here is a CodePen from a Technical Interview that I did a little while ago where I use an IIFE to initialize event listeners and handler functions for a flyout menu:
Avoiding Global Namespace Pollution
In JavaScript, when you create a function or var
variable with a declaration statement outside of any other context, that function or variable gets added to the global object.
Say we want to greet our user by name:
var userName = prompt("What is your name?"); // input "Joey"
function sayHello(name) { console.log(`Hi there ${name}!`);}
sayHello(userName);// expected result: "Hi there Joey!"console.log(window.userName);// expected result: "Joey"console.log(sayHello);// expected result: function sayHello(name) { ...etc.. }
In this example we've created a var
variable, userName
, and assigned it the value that a user inputs to the prompt
, 'What is your name?'
.
After that we define a function named sayHello()
, which accepts a name
parameter, and greets the user by name.
Below that, we run the function, passing it the userName
variable as its argument.
Finally, we console.log(window.userName)
and sayHello
and see that we have polluted our window
scope with the userName
variable and the sayHello()
function.
If we're only going to run this code once and never again, it doesn't make a lot of sense to have the window
carrying around a userName
variable or the sayHello()
function in its global scope for the entire time our application runs. Instead, we can use an IIFE and not pollute the global scope:
(function sayHello() { var userName = prompt("What is your name?"); console.log(`Hi there ${userName}!`);})();console.log(window.userName);// expected result: undefinedconsole.log(sayHello);// expected result: ReferenceError: sayHello is not defined
Because var
variables are scoped to the function that they're declared in, the userName
variable only exists within the sayHello()
function, which only exists within the IIFE. This is key to understanding the benefits of IIFEs: that before the introduction of let
and const
variables or the arrival of ES Modules and CommonJS, the IIFE enabled a developer to execute code that had a safe scope without polluting the global scope or creating an entire other function (along with a function call).
Aliasing Global Variables
This pattern also helps us to make sure we're working with the correct global variables. Say we're using a number of JavaScript libraries, specifically bling.js and jQuery. Both of these libraries alias the $
dollar sign to represent the global object. In this made-up example, if you started using the $
variable, it might be hard to determine which library you would actually be referencing.
To safely use one library or the other, you can alias the $
variable name within an IIFE:
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script><script> (function ($) { // you're safely using jQuery in here console.log($); // expected result: function ( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced'... etc. // } })(jQuery);</script>
After importing jQuery from its CDN, I simply passed jQuery
as an argument to the IFFE, with the $
as its parameter. Within the IIFE we are free to use the $
. If we check the content of the $
with console.log($)
, we see that we are in fact getting jQuery.
This way, even if we are also (for some reason) using bling.js...
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script><script> /* bling.js */ window.$ = document.querySelectorAll.bind(document);
Node.prototype.on = window.on = function (name, fn) { this.addEventListener(name, fn); };
NodeList.prototype.__proto__ = Array.prototype;
NodeList.prototype.on = NodeList.prototype.addEventListener = function ( name, fn ) { this.forEach(function (elem, i) { elem.on(name, fn); }); }; console.log($); // expected result: querySelectorAll() { [native code ]} (function ($) { // you're safely using jQuery in here console.log($); // expected result: function ( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced'... etc. // } })(jQuery);</script>
...we know that we are safely using jQuery within the IIFE, since we have explicitly aliased jQuery to the $
parameter within that IIFE. If you want to be even more explicit, you could alias jQuery to a different variable to ensure there's no confusion:
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script><script> (function (_) { // you've aliased jQuery to the underscore symbol console.log(_); // expected result: function ( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced'... etc. // } })(jQuery);</script>
In this example I have aliased jQuery to the _
underscore character, so now I would be using that for the querySelector operations within the IIFE.
Modules
Prior to ES Modules, CommonJS, and other modern module solutions, IIFEs were a preferred pattern for writing code modules. The idea behind a module is that you can write all the code related to some specific functionality in one place, and import that file wherever it's needed. There are now more modern solutions for modular code, but IIFEs helped developers avoid name collisions and write public and private methods within modules in earlier programs.
Avoiding Naming Collisions
Similar to our example above with the $
variable being overloaded when we imported both bling.js and jQuery, collisions with variable and function names can also come into play when you start modularizing your code and importing third party libraries if those libraries/modules use the same variable or function names.
Say, for example, you're writing a Tax and Tip Calculator app. Inside of a calculator.js
file you have three functions:
// in calcuator.js:function applyTax(subtotal, taxRate = 0.08) { return subtotal * taxRate;}
function applyTip(subtotal, tipRate = 0.25) { return subtotal * tipRate;}
function calculateBill(total) { return total + applyTip(total) + applyTax(total + applyTip(total));}
You can import and use this file as a basic module like so:
<script src="calculator.js"></script><script> const bill = calculateBill(100); console.log(bill); // expected result: 135</script>
However, as your application gets larger you might find yourself adding another JavaScript library. We'll import a made up library with a file called library.js
. Your HTML now might look something like this:
<script src="calculator.js"></script><script src="library.js"></script><script> const bill = calculateBill(100);</script>
Great! However, what happens when libarary.js
also contains a function called calculateBill
?
// in library.js:function calculateBill() { return "haha wrong function";}
At this point if we try to calculate our bill...
<script src="calculator.js"></script><script src="library.js"></script><script> const bill = calculateBill(100); console.log(bill); // expected result: "haha wrong function"</script>
...we just get "haha wrong function"
, as the calculateBill
function within library.js
overrides the one found in calculator.js
. That's not good! And since it's a third-party library, we don't own the code and can't easily change it. Getting the maintainer of library.js
to change the name of their calculateBill()
function would probably be a lot of work, and might introduce breaking changes for them or other applications using their code.
To make sure we're using the correct function, let's make some adjustments to calculator.js
to modularize the code. To do so, let's wrap the applyTax
, applyTip
, and calculateBill
functions within an IIFE. We'll also create a variable named calculator
and assign that IIFE as its value. Finally, the function expression will return the calculateBill
function as an available method.
// in calculator.js:var calculator = (function Calculate() { function applyTax(subtotal, taxRate = 0.08) { return subtotal * taxRate; } function applyTip(subtotal, tipRate = 0.25) { return subtotal * tipRate; } function calculateBill(total) { return total + applyTip(total) + applyTax(total + applyTip(total)); } return { calculateBill, };})();
At this point, calcuateBill
is available on the calculator
object as a method, and we are able to make isolated calls to calculator
's calculateBill
method and the calculateBill()
function within library.js
like so:
<script src="calculator.js"></script><script src="library.js"></script><script> const calculatorBill = calculator.calculateBill(100); console.log(calculatorBill); // expected result: 135
const libraryBill = calculateBill(); console.log(libraryBill); // expected result: "haha wrong function" - comes from library.js</script>
Writing Public and Private Methods
What's neat about this pattern is that with a little bit of work we are able to more deterministically use functionality from a lot of different places, even if method names are similar or identical. Within the IIFE in calculator.js
, we explicitly returned the calculateBill
method. Doing so makes the calculateBill()
functionality available to our broader program.
However, if we try to access one of the other functions that isn't explicitly returned from that IIFE...
<script src="calculator.js"></script><script src="library.js"></script><script> const appliedTax = calculator.applyTax(100); console.log(appliedTax); // expected result: TypeError: calculator.applyTax</script>
...then we get an error telling us that applyTax
isn't a function. Because we haven't explicitly returned it within the IIFE's function expression, it's not an available method. It is, however, a function that exists within the IIFE and gets used by the calculateBill
method. This demonstrates how we can compose methods that are both Private (available for use within the module but nowhere else) and Public (available for use outside of the module).
Using the Index within for
Loops
A little while back I had a post called Frontend JavaScript Pop Quiz, where I investigated why a for
Loop wasn't working correctly.
Here's the for
Loop in question:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Frontend JavaScript Pop Quiz</title> </head> <body> <script> for (var i = 0; i < 5; i++) { var btn = document.createElement("button"); btn.appendChild(document.createTextNode("Button " + i)); btn.addEventListener("click", function () { console.log(i); }); document.body.appendChild(btn); } </script> </body></html>
After the loop appends the buttons to the page, i
is set to 5
, so when you click on any of the buttons (even if you're clicking on 1
or 3
), the button logs out the number 5
.
The solution I proposed in that post was to change var
to let
, and to break the event listener out, connecting it to a query selector. Also, using data-
attributes. You should read that post if you're curious.
But that's an ES6 solution. Prior to ES6, we could use the IIFE pattern:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Frontend JavaScript Pop Quiz</title> </head> <body> <script> for (var i = 0; i < 5; i++) { var btn = document.createElement("button"); btn.appendChild(document.createTextNode("Button " + i)); btn.addEventListener( "click", (function (copyOfI) { return function () { console.log(copyOfI); }; })(i) ); document.body.appendChild(btn); } </script> </body></html>
Now when we click any of the buttons, our program logs out the correct corresponding number. This pattern isn't ideal though, as the var i
variable is still globally defined. I also find returning a function within the IIFE to be kind of clunky and hard to read.
Using await
and for-await
in older environments
In older environments that don't have a top level await
, you can wrap async
/ await
or async
/ for await
code patterns inside of an IIFE to make them run immediately.
Where in newer environments you are able to write:
const jsonResponse = fetch("../data/structured-data.json").then((response) => response.json());
export default await jsonResponse;
The same could be written using an IIFE for older environments:
var getJsonResponse = async function (endpoint) { /* implement fetch here */};
(async function () { var jsonResponse = await getJsonResponse("../data/structured-data.json"); for await (var data of jsonResponse) { console.log(data); }})();
IIFE Summary
So what is an IIFE? An Immediately Invoked Function Expression, or IIFE, is a pattern used to run some code immediately and allows a developer to declare variables and functions that don't pollute the global namespace.
An IIFE can be written as a classic function expression, an arrow function expression, or an async function expression. They can be named or anonymous, be given arguments, and have their second set of parentheses either inside or outside of the first set. Alternate IIFE grammar patterns can also use certain JavaScript operators, or pass the IIFE to a place where an expression is expected to enforce the IIFE. Finally, it's common practice to place ;
a semicolon in front of an IIFE. This helps avoid any problems that may result from automatic semicolon insertion.
IIFEs are beneficial when you need to run some code immediately, avoid global scope and namespace pollution, create a module pattern with private and public variables and methods, ensure your for
Loop index refers to the correct iteration, and immediately use await
and for-await
in older environments that don't have a top-level await
available.
IIFEs are sort of a dated practice now that ES6, ES Modules, Common JS, Private Class Methods, and many other newer language features have broad support from browsers and Node. However, understanding IIFEs is not only good for understanding the problems that these newer patterns solve, but if you work in older code bases or open source projects, you're bound to run into an IIFE. Hopefully this post will help you understand what that IIFE has been helping its project accomplish.