Use Cases for JavaScript's IIFEs
I recently wrote about the syntax of JavaScript's IIFE pattern and explained why we write immediately invoked function expressions the way we do. Several readers criticized the post for being out of date, though, arguing that block-scoped variables as introduced by ECMAScript 2015 make IIFEs obsolete.
Quite the contrary is true — the IIFE pattern is not obsolete at all! For this reason, I decided to write this follow-up post to showcase a variety of common use cases for immediately invoked function expressions. Note that this list is far from complete, so I hope you don't have any hard feelings if your favorite use case doesn't appear here.
#Function Scoping vs. Block Scoping
Local variables declared using the var
keyword are scoped to the enclosing function. If no such function exists, the variables will be created as global variables instead, thus polluting the global scope. To prevent this, we can use an IIFE to create a function wrapper for local variables:
(function () {
var foo = "bar";
console.log(foo);
})();
foo; // ReferenceError: foo is not defined
The argument now is that instead of using an IIFE, we can use block-scoped variables to achieve the same result. Introduced by ECMAScript 2015, the let
and const
keywords declare local variables that are scoped to the enclosing block rather than the enclosing function:
{
let foo = "bar";
console.log(foo);
}
foo; // ReferenceError: foo is not defined
However, block-scoped variables are not a replacement for immediately invoked function expressions. Yes, let
and const
can be used to restrict the visibility of local variables to the surrounding block — if ECMAScript 2015 is supported, that is!
If, however, you're running your JavaScript code in an environment that doesn't support ECMAScript 2015 yet (such as older browsers, for example), you can't use the new let
and const
keywords for creating block-scoped local variables. You'll have to resort to classic function scoping in this case.
#Closures and Private Data
Another use case for an IIFE is to provide a wrapping scope around a local variable that is accessed by a function returned from the IIFE. This way, a closure is created that enables the function to access the local variable even when that function is executed outside of the IIFE's lexical scope.
Assume we want to create a function uniqueId
that returns a unique identifier (like "id_1", "id_2", and so on) every time it's called. Within the IIFE, we'll be keeping track of a private counter variable that is incremented every time the counter function is called. We return from the IIFE another function that returns a new identifier string when called:
const uniqueId = (function () {
let count = 0;
return function () {
++count;
return `id_${count}`;
};
})();
console.log(uniqueId()); // "id_1"
console.log(uniqueId()); // "id_2"
console.log(uniqueId()); // "id_3"
Note that the count
variable is inaccessible from outside of the IIFE. Except for the function that's being returned, nobody can read or modify the count
variable. This allows for the creation of truly private state that can only be modified in a controlled fashion. The revealing module pattern relies heavily on this mechanism:
const counter = (function () {
let counterValue = 0;
return {
increment() {
++counterValue;
},
get value() {
return counterValue;
},
};
})();
counter.increment();
console.log(counter.value); // 1
counter.increment();
counter.increment();
console.log(counter.value); // 3
Neither let
nor const
is a replacement for an IIFE returning a function that closes over some local variables to manage private data.
#Aliasing Variables
Sometimes, you might be in the situation that you're using two different libraries that expose a global variable with the same name. For instance, consider that you're using jQuery and another library that also assigns to the $
global variable.
To resolve this naming conflict, you can wrap a piece of code with an IIFE that passes one of the global variables (e.g. jQuery
) as an argument. Within the function, you can then refer to the value by a parameter name (e.g. $
) of your choosing:
window.$ = function somethingElse() {
// ...
};
(function ($) {
// ...
})(jQuery);
Within the IIFE, the $
parameter refers to the jQuery function and shadows whatever value has been assigned to $
in the outer scope.
#Capturing the Global Object
Depending on where your JavaScript code runs, you'll have a different global object. When running in the browser, the global object is window
. Node.js, on the other hand, uses the global
object. Since you don't want to hardcode either one of those names when writing universal JavaScript code, you can use a wrapper like this:
(function (global) {
// ...
})(this);
The global
parameter will refer to the correct global object in both a browser and a Node.js environment. Check out this post by Todd Motto for more details on capturing the global object using this technique.
#Optimization for Minification
The approach of aliasing variable names can also be used to optimize code such that it can be minified more efficiently. Take this common wrapper, for example:
(function (window, document, undefined) {
// ...
})(window, document);
A JavaScript minifier like UglifyJS can now shorten the function's parameter names to single-letter identifiers:
(function (w, d, u) {
// ...
})(window, document);
The idea is that shorter identifier names result in a smaller file size. However, if HTTP responses are compressed using Gzip or Deflate, the file size is reduced very effectively anyway. Hence, the marginal gains of this minification technique are lower if used in conjunction with compression algorithms. The shorter names might still pay off, though, so measure and compare the response sizes for yourself.