IIFEs: Immediately Invoked Function Expressions
Prior to the addition of modules to the JavaScript language, the best
practice to get the desired separation and sharing was to use the
namespace of functions. So, all the the global variables, like tan
,
instead become local variables of a function. We saw this in our
reading on Modules, in the section on namespaces and
functions with a function
called namespace2
.
In this supplemental reading, we'll see how functions can be used as namespaces, giving us both the separation and sharing that we want, though it'll be tougher going than JavaScript's module system.
This reading explains and expands on "The Module Pattern", which starts on page 165 of our book.
There are two approaches I could take with this presentation: I could start with properties of functions and build a module system from first principles, or I could try to bridge the gap between the modules we want and the functions we have. I'll do a little of both.
Functions as Containers¶
Let's start with functions. As you know, a function can have local variables (even ones with the same name as global variables) and those variables are distinct and separate. A function is its own little world.
Here's a function that adds some special sauce to its argument. The sauce is separate from the global variable with the same name:
var sauce = 5;
function addSauce(x) {
var sauce = 3;
return x + sauce;
}
Invoking addSauce(1)
returns 4, not 6.
Thus, a function creates a namespace.
Globals¶
But what if we really need a global variable? We might need a global to count something, say the number of times a user has clicked on something or the number of times a function has been invoked.
Let's take a specific example. Suppose we want to annoy the user, playing a prank on them, requiring them to feed the cookie monster some number of cookies. To feed the cookie monster, we pop up an alert every 5 seconds that says "give me a cookie" and user has to click "okay". So that it eventually stops, we count using a global variable. Here's the code.
var maxCount = 10;
var currCount = 0;
function cookieMonster() {
currCount ++;
if( currCount < maxCount ) {
alert('Give me a cookie!');
setTimeout(cookieMonster, 5000);
}
}
// start it
setTimeout(cookieMonster, 5000);
Note that the code uses the built-in function setTimeout
which runs
a function of our choosing every N milliseconds (5000 milliseconds is
5 seconds), where the function is the first argument and the delay is
the second argument. You'll see that function again in a more
reasonable way when we do automatic slide shows.
Enclosing the Globals¶
The prank code works fine. (Copy/paste it into your browser console
if you want. You can stop it by closing or reloading that browser
tab.) But suppose that we want to avoid the global variables. First of
all, this is an example of modules. Secondly, maybe there are other
things we want to count, or maybe we want to keep the user from
modifying the currCount
and maxCount
variables.
What we can do is turn all of these names (the two variables and the function) into local names of a surrounding "wrapper" function that they are all inside:
function prank() {
var maxCount = 10;
var currCount = 0;
function cookieMonster() {
currCount ++;
if( currCount < maxCount ) {
alert('Give me a cookie!');
setTimeout(cookieMonster, 5000);
}
}
// start it
setTimeout(cookieMonster, 5000);
}
prank();
Again, you can try this; it works.
IIFE¶
You'll notice our prank
function is defined (globally) only to be
immediately invoked. The main purpose of naming something is to refer
to it later or elsewhere, but neither of those really apply here.
Here's a concrete example. In a function to convert fahrenheit to celcius, which do you prefer:
function fahrenheit2celsius(f) {
var diff = f - 32;
return 1.8 * diff;
}
function fahrenheit2celsius(f) {
return 1.8 * (f - 32);
}
If you like the extra variable and the extra line of code, you're welcome to do so, but you can see that it's not necessary, and the latter code is certainly acceptable.
We can do the same thing with our prank
function. Instead of naming
it, we'll make it anonymous, but we'll wrap the definition in
parentheses and then use a pair of parentheses to invoke it:
(function () {
var maxCount = 10;
var currCount = 0;
function cookieMonster() {
currCount ++;
if( currCount < maxCount ) {
alert('Give me a cookie!');
setTimeout(cookieMonster, 5000);
}
}
// start it
setTimeout(cookieMonster, 5000);
})();
Technically, by wrapping the definition in parentheses, we've converted a function statement into an expression (something that has a value), and we are immediately invoking it. Thus, we have an immediately invoked function expression. That's such a cumbersome phrase, that it's just called an IIFE. Wikipedia has more about Immediately Invoked Function Expressions.
Using an IIFE means that no names are added to the global namespace,
so the code has no footprint at all. (The footprint
of code is
the set of names that are added to the global JavaScript namespace.)
Abstractly, an IIFE is like this:
(function () {
...
})();
Observations:
- the punctuation on that last line is terrifying
- the IIFE is just a simple wrapper around normal code. We didn't change our prank code at all. We just cut/pasted it into an IIFE.
- No names from the inside of the IIFE leak out into the global namespace. The IIFE doesn't define any global names. Indeed, if it weren't for the alerts, it would be as if this code didn't exist.
The last point is worth emphasizing: an IIFE gives us a completely separate namespace in an easy way, just requiring two extra lines of code, before and after our code.
IIFEs Today¶
In fact, IIFEs are so good at creating these little worlds with their own namespaces, and without the need for separate files, imports and exports, and all that, that IIFEs are still used today for lightweight, easy modules.
Sharing¶
What we want out of a module system is separation and sharing. The IIFE we had above works very well at the separation part, but how can it share?
Two techniques are possible and are used. One is to return a value
from the IIFE that contains all the things we want to share. For
example, our prank code above runs automatically. What if we want to
load the code, keep its global variables separate, but share the
cookieMonster
function? We could return the function, and allow the
caller to assign it to variable of their choosing. This requires us to
put the IIFE on the right hand side of an assignment statement:
var myPrank = (function () {
var maxCount = 10;
var currCount = 0;
function cookieMonster() {
currCount ++;
if( currCount < maxCount ) {
alert('Give me a cookie!');
setTimeout(cookieMonster, 5000);
}
}
return cookieMonster;
})();
Then, when we want to run the prank, we invoke the myPrank
function.
This can work, but the syntax is awkward, and it doesn't scale. What
if we wanted to export two or more functions, say startPrank
and
stopPrank
? While it's possible to do more in this direction, our
book uses a different approach, which we'll turn to now.
Creating Global Names¶
This section explains what's going on in section "Adding Modules to a Namespace", starting on page 170 of our book.
As we learned in the main reading, there's a global namespace:
- it's an object
- all global variables are properties of that object and
- all properties of that object are global variables
In browsers at the time our book was written, that global object is
the value of window
. Therefore, if our prank code wanted to create
two globals, it can do so by assigning to properties of window
, like
this:
(function () {
...
function startPrank() { ... }
function stopPrank() { ... }
// create some globals
window.startPrank = startPrank;
window.stopPrank = stopPrank;
})();
Those final assignment statements are a little like the export
statements in modern JavaScript modules, since they have the effect of
making certain names from the "inside" available on the "outside".
However, there's a small problem. You knew it wasn't going to be that easy, didn't you?
The Variable with the Global Environment¶
It turns out that different JavaScript implementations used different
variables to hold that global environment. Browsers used window
, as
we know. Web Workers used self
. Node.js used this
. What to do
about this heterogeneous world?
(One solution is to change the language to introduce a standard name
for the global environment. That's the genesis of
globalThis,
which we used in the main modules
reading. The globalThis
variable didn't exist in JavaScript at the
time our book was written.)
Given the lack of a consistent name, how can we write code that can be easily made to work in Web Workers, Node.js and a browser?
You might think that all we have to do is global search and replace,
renaming all the occurrences of window
to some other name. We could,
but that would be error prone. Suppose we have a variable named
windowShade
. Do we really want it renamed to be thisShade
?
Probably not. But it turns out we don't need that. We can use ability
of functions to rename values.
Functions Can Rename¶
Functions create a local namespace, and that namespace can be a new set of names. We can use that to do a kind of structured renaming.
Suppose we choose a neutral name for the global environment, not
window
, self
, or this
. Let's say we use glob
to hold it. Our
export statements then become:
// create some globals
glob.startPrank = startPrank;
glob.stopPrank = stopPrank;
Okay, but how do we actually make that neutral code work? Imagine
creating a simple, anonymous function that takes one argument, called
glob
, which we can bind to whatever value we want:
function (glob) {
// create some globals
glob.startPrank = startPrank;
glob.stopPrank = stopPrank;
}
Now, if that code is running in a browser, we want glob
to have the
value window
. That's easy enough:
(function (glob) {
// create some globals
glob.startPrank = startPrank;
glob.stopPrank = stopPrank;
})(window);
See how the function is invoked with window
as its argument? That
means glob
will have window
as its value and our code works
perfectly.
If we decide to port our code to run in, say, Web Workers which uses
self
, we only have to change that last line:
(function (glob) {
// create some globals
glob.startPrank = startPrank;
glob.stopPrank = stopPrank;
})(self);
Our book's authors, however, decided again choosing a "neutral" name
like glob
. First of all, trying to come up with a neutral name is
problematic at best: it's a new name to clutter up our minds. Second,
we're writing code for a normal browser, not for for Web Workers or
Node.js, so our variable will always have the value of window
. We
just want to allow for the possibility that it might have a
different value. Third, we'd have to remember that glob
means
window
.
So, with that in mind, our authors used the renaming trick, but they used
window
as the local name. So their code looks like:
(function (window) {
// create some globals
window.startPrank = startPrank;
window.stopPrank = stopPrank;
})(window);
This can be confusing, so let's pause to think about this for a
moment. The window
on the last line is outside our IIFE and is the
normal window
variable, which contains the global environment. The
window
on the first line is inside our IIFE, and is just as much a
local variable of the module as maxCount
, currCount
,
startPrank
, stopPrank
or anything else inside our IIFE.
Our book does this all the time, so it's worth thinking it through at least once.
If we wanted to run this code on Web Workers, we would just change the last line:
(function (window) {
// create some globals
window.startPrank = startPrank;
window.stopPrank = stopPrank;
})(self);
That shows the renaming in action: the function invocation renames
window
to self
throughout the IIFE.
Creating A Module¶
Our book's authors wants to do a few more things, namely
- have the modules put themselves in a global
App
variable, and - allow the modules to be loaded in any order.
The first goal is nice, because then everything is available via
just one variable, App
, so the program's possible conflicts with
other code is minimized. The second is also nice because it means one
less thing to remember as we write our main HTML page or our main
module.
Without the goal of order-independent loading of files, it would be straightforward to create the object and have each file add to it. The first module file creates the object:
var App = {}; // local variable with empty object at first
App.CheckList = CheckList; // store something in the App
window.App = App; // make the App object global
The subsequent modules do this:
var App = window.App; // local variable with the global App object
App.DataStore = DataStore; // store something in the App
window.App = App; // make the App object globala
Notice that the last lines are all the same; they don't care whether they are loaded first or not. Only the first line is affected.
That first line initializes a local variable named App
to contain
either a new, empty object, or the global App
object that some prior
module created. The last line sets the global App
variable to be the
same object as the local variable of the same name.
So, how can we write code that handles either being the first module
or a subsequent module? It turns out that, if the global value doesn't
exist, it counts as false
, so we can use a logical or operator
(||
) to give a default value if the global value doesn't exist.
Therefore, all our modules start with this:
var App = window.App || {}; // init local variable
In English, this just says "use the existing value if there is one, otherwise, use a new, empty object". Since all of our modules start this way, they can be loaded in any order.
Final Words¶
Here's the code from page 171 of our book:
(function (window) {
'use strict';
var App = window.App || {};
function DataStore() {
console.log('running the DataStore function');
}
App.DataStore = DataStore;
window.App = App;
})(window);
Eleven lines of code that don't make a lot of sense without a deep understanding of IIFEs, namespaces, the global environment and default values. Hopefully, that code makes a bit more sense to you now.
Rewritten Coffeerun¶
Note that in my re-write of the Coffeerun app, I didn't write each
module to add itself to the App
object. Instead, I had the
main-module.js
, which loads all the modules (order-independent), add
each to the App
object:
import { CheckList, makeDescription, makeRowElt } from './checklist-module.js';
import { Validation } from './validation-module.js';
import { FormHandler } from './formhandler-module.js';
import { RemoteDataStore } from './remotedatastore-module.js';
import { DataStore } from './datastore-module.js';
import { Truck } from './truck-module.js';
var App = {}; // initially empty
globalThis.App = App; // for debugging
// Add these names to the App
App.CheckList = CheckList;
App.makeDescription = makeDescription;
App.makeRowElt = makeRowElt;
App.Validation = Validation;
App.FormHandler = FormHandler;
App.RemoteDataStore = RemoteDataStore;
App.DataStore = DataStore;
App.Truck = Truck;
I did this partly for simplicity in the various modules and also to
avoid having the modules depend on any expectations of being part of
something called App
. A Python module doesn't add itself to
something called App
but is more generic. These rewritten modules
are more in that spirit.