Mastering JavaScript Functional Programming
上QQ阅读APP看书,第一时间看更新

Immediate invocation

There's yet another common usage of functions, usually seen in popular libraries and frameworks, that lets you bring into JS (even the older versions!) some modularity advantages from other languages. The usual way of writing this is something like the following:

(function() {
// do something...
})();

Another equivalent style is (function(){ ... }()) -- note the different placement of the parentheses for the function call. Both styles have their fans; pick whichever suits you, but just follow it consistently.

You can also have the same style, but passing some arguments to the function, which will be used as the initial values for its parameters:

(function(a, b) {
// do something, using the
// received arguments for a and b...
})(some, values);

Finally, you could also return something from the function:

let x = (function(a, b) {
// ...return an object or function
})(some, values);

The pattern itself is called, as we mentioned, Immediately Invoked Function Expression -- usually simplified to IIFE, pronounced iffy. The name is easy to understand: you are defining a function and calling it right away, so it gets executed on the spot. Why would you do this, instead of simply writing the code inline? The reason has to do with scopes.

Note the parentheses around the function. This helps the parser understand that we are writing an expression. If you were to omit the first set of parentheses, JS would think you were writing a function declaration instead of an invocation.  The parentheses also serve as a visual note, so readers of your code will immediately recognize the IIFE. 

If you define any variables or functions within the IIFE, because of JS's function scope, those definitions will be internal, and no other part of your code will be able to access it. Imagine you wanted to write some complicated initialization, like the following:

function ready() { ... }
function set() { ... }
function go() { ... }
// initialize things calling ready(),
// set() and go() appropriately

What could go wrong? The problem hinges on the fact that you could (by accident) have some function with the same name of any of the three here, and hoisting would imply that the latter function would be called:

function ready() {
console.log("ready");
}
function set() {
console.log("set");
}
function go() {
console.log("go");
}
ready();
set();
go();

function set() {
console.log("UNEXPECTED...");
}
// "ready"
// "UNEXPECTED"
// "go"

Oops! If you had used an IIFE, the problem wouldn't have happened. Also, the three inner functions wouldn't even be visible for the rest of the code, which helps keeping the global namescape less polluted:

(function() {
function ready() {
console.log("ready");
}
function set() {
console.log("set");
}
function go() {
console.log("go");
}
ready();
set();
go();
})();

function set() {
console.log("UNEXPECTED...");
}
// "ready"
// "set"
// "go"

To see an example involving returned values, we could revisit the example from Chapter 1, Becoming Functional - Several Questions, and write the following, which would create a single counter:

const myCounter = (function() {
let count = 0;
return function() {
count++;
return count;
};
})();

Then, every call myCounter() would return an incremented count -- but there is no chance that any other part of your code will overwrite the inner count variable because it's only accessible within the returned function.