JavaScript's Immediately Invoked Function Expressions

Introduction

Defining and calling functions are key practices for mastering JavaScript and most other programming languages. Usually, a function is defined before it is called in your code.

Immediately-Invoked Function Expressions (IIFE), pronounced "iffy", are a common JavaScript pattern that executes a function instantly after it's defined. Developers primarily use this pattern to ensure variables are only accessible within the scope of the defined function.

In this article, you will first learn about function expressions. After, we will go further in-depth into IIFEs - how to write them and when to use them. Finally, we'll discuss how the let keyword introduced in ECMAScript 6 provides a cleaner alternative for some IIFE use-cases.

What are Function Expressions?

In JavaScript, you can define a function in 2 different ways:

  1. A declaration
  2. An expression

Function Declarations begin with the function keyword, followed by the name of the function and any arguments it may take. For example, we can create a logName function using a declaration like this:

function logName(userName) {
    console.log(`${userName}, you are awesome`);
};

logName("Jane");

From the function's definition, we log any given value in the message parameter to the console. We then called the function with "Jane, you are awesome!", which will print that text to the console.

When defining a function using function declarations, the function is hoisted. A hoisted function or variable is placed at the top of their functional scope when JavaScript is executing code.

Note: Functional Scope refers to where a function was defined. For example, if a function foo() contained a function bar() inside of it, we would say that the functional scope of bar() is foo(). If foo() was not defined within a function, then foo() belongs to the global scope. JavaScript functions in the global scope are accessible to all the code that runs with it.

In practice, this behavior allows you to use a function before defining it. For example, the previous code can be rewritten like this and would behave the same way:

logName();

function logName(name) {
    console.log(`${name}, you are awesome!`);
};

Function Expressions are functions definitions that are assigned to a variable. Therefore, our logName() function declaration can become a function expression if we created it like this:

const logUserName = function logName(name) {
    console.log(`${name}, you are awesome!`);
};

logUserName("Jane");

In this example, to call the function we need to use the variable name that was provided - logUserName. This does not change the behavior of the function, it still logs "You are awesome" to the console.

Unlike function declarations, function expressions are not hoisted. These functions are only available when the JavaScript interpreter processes that line of code.

For example, if we tried to call logUserName() before creating it as a function expression:

logUserName("Jane");
const logUserName = function logName(name) {
    console.log(`${name}, you are awesome!`);
};

We get the following error:

Uncaught ReferenceError: Cannot access 'logUserName' before initialization

Another difference between function expressions and function declarations is that function expressions can define functions without a name.

Functions without names are called anonymous functions. For example, logUserName() could also be defined with an anonymous function like this:

const logUserName = function (name) {
    console.log(`${name}, you are awesome!`);
};

Arrow Functions

Arrow functions provide syntactic sugar for Function Expressions. A re-implementation of our logUserName function using an arrow function would look like this:

const logUserName = (name) => {
    console.log(`${name}, you are awesome!`);
}

Read Arrow Functions in JavaScript to learn more about this syntax, and how it affects function scope.

Now that we know how to create various function expressions, let's learn how to immediately invoke them.

What are Immediately-Invoked Function Expressions?

IIFEs are functions that are executed immediately after being defined.

We can make any function expression an IIFE by wrapping it in parentheses, and adding a following pair of parentheses at the end:

(function() {
    // Code that runs in your function
})()

Alternatively, you can use the arrow syntax to create an IIFE as follows:

(() => {
    // Code that runs in your function
})()

The parentheses surrounding the function definition lets JavaScript know that it will process a function expression. The last pair of parentheses invoke the function.

Syntax Variations

You can create IIFEs without the first set of parentheses if you use a unary operator - special characters that tell JavaScript to evaluate the following expression.

We can create function expressions with unary operators like this:

+function () {
    // Code that runs in your function
}();

-function () {
    // Code that runs in your function
}();

!function () {
    // Code that runs in your function
}();

~function () {
    // Code that runs in your function
}();

void function () {
    // Code that runs in your function
}();

It's important to note that these operators could affect any data returned by your function. For example, the code below looks like it should return 10, but it actually returns -10:

$ node
> -function () {return 10;}();
-10
>

Since this unary syntax is less common and can be confusing to developers, it is generally discouraged.

IIFEs can also take functional arguments. We can pass variables into the scope as shown below:

(function(arg1, arg2) {
    // Code that runs in your function
})("hello", "world");

Now that we have seen how to create IIFEs, let's look at common situations where they are used.

When to Use an IIFE?

The most common use cases for IIFEs are:

  • Aliasing global variables
  • Creating private variables and functions
  • Asynchronous functions in loops

Aliasing Global Variables

If you have 2 libraries that export an object with the same name, you can use IIFEs to ensure they don't conflict in your code. For example, the jQuery and Cash JavaScript libraries both export $ as their main object.

You can wrap your code within an IIFE that passes one of the global variables as an argument. Let's say, we want to ensure that $ refers to the jQuery object, and not the cash alternative. You can ensure that's jQuery is used with the following IIFE:

(function($) {
    // Code that runs in your function
})(jQuery);

Creating Private Variables and Functions

We can use IIFEs to create private variables and functions within the global scope, or any other function scope.

Functions and variables added to the global scope are available to all scripts that are loaded on a page. Let's say we had a function generateMagicNumber(), which returned a random number between 900 and 1000 inclusive, and a variable favoriteNumber in our JavaScript file.

We can write them like this:

function generateMagicNumber() {
    return Math.floor(Math.random() * 100) + 900;
}

console.log("This is your magic number: " + generateMagicNumber());

var favoriteNumber = 5;
console.log("Twice your favorite number is " + favoriteNumber * 2);

If we load other JavaScript files in our browser, they also gain access to generateMagicNumber() and favoriteNumber. To prevent them from using or editing them, we encase our code in an IIFE:

(function () {
    function generateMagicNumber() {
        return Math.floor(Math.random() * 100) + 900;
    }

    console.log("This is your magic number: " + generateMagicNumber());

    var favoriteNumber = 5;
    console.log("Twice your favorite number is " + favoriteNumber * 2);
})();

It runs the same, but now generateMagicNumber() and favoriteNumber are only accessible in our script.

Asynchronous Functions in Loops

JavaScript's behavior surprises many when callbacks are executed in loops. For example, let's count from 1 to 5 in JavaScript, putting a 1 second gap between every time we log a message. A naïve implementation would be:

for (var i = 1; i <= 5; i++) {
    setTimeout(function () {
        console.log('I reached step ' + i);
    }, 1000 * i);
}

If you run this code, you will get the following output:

$ node naiveCallbackInLoop.js
I reached step 6
I reached step 6
I reached step 6
I reached step 6
I reached step 6

While the output would be printed 1 second after the other, each line prints that they reached step 6. Why?

When JavaScript encounters asynchronous code, it defers execution of the callback until the asynchronous task completes. That's how it remains non-blocking. In this example, the console.log() statement will run only after the timeout has elapsed.

JavaScript also created a closure for our callback. Closures are a combination of a function and its scope when it was created. With closures, our callback can access the variable i even though the for loop has already finished executing.

However, our callback only has access to the value of i at the time of its execution. As the code within the setTimeout() function were all deferred, the for loop was finished with i being equal to 6. That's why they all log that they reached step 6.

This problem can be solved with an IIFE:

for (var i = 1; i <= 5; i++) {
    (function (step) {
        setTimeout(function() {
            console.log('I reached step ' + step);
        }, 1000 * i);
    })(i);
}

By using an IIFE, we create a new scope for our callback function. Our IIFE takes a parameter step. Every time our IIFE is called, we give it the current value of i as its argument. Now, when the callback is ready to be executed, its closure will have the correct value of step.

If we run this code snippet, we will see the following output:

$ node iifeCallbackInLoop.js
I reached step 1
I reached step 2
I reached step 3
I reached step 4
I reached step 5

While IIFEs solves our problem with minimal code changes, let's have a look at how ES6 features can make it easier to run asynchronous code in loops.

Block Scoping with let and const

ES6 added the let and const keywords to create variables in JavaScript. Variables declared with let or const are block-scoped. This means that they can only be accessed within their enclosing block - a region enclosed by curly braces { }.

Let's count from 1-5 in 1 second intervals using the let keyword instead of var:

for (let i = 1; i <= 5; i++) {
    setTimeout(function () {
        console.log('I reached step ' + i);
    }, 1000 * i);
}

We will get the following output when we run this code:

$ node es6CallbackInLoop.js
I reached step 1
I reached step 2
I reached step 3
I reached step 4
I reached step 5

Now that the variable i is block-scoped, the closures for our callback function get the appropriate value of i when they eventually execute. This is more concise than our IIFE implementation.

Using let is the preferred way to execute asynchronous functions in a loop,

Conclusion

An Immediate-Invoked Function Expression (IIFE) is a function that is executed instantly after it's defined. This pattern has been used to alias global variables, make variables and functions private and to ensure asynchronous code in loops are executed correctly.

While popular, we have seen how changes in ES6 can eliminate the need to use IIFEs in modern JavaScript. However, mastering this pattern also gives us a deeper understanding of scope and closure, and will be especially useful in maintaining legacy JavaScript code.

Author image
About Allan Mogusu
Nairobi, Kenya Twitter