https://www.kirupa.com/html5/immediately_invoked_function_expressions_iife.htm
function variation 1 – declare named function, then execute it
1 2 3 4 5 6 7 8 9 10 11 12 |
function areYouLucky() { var foo = Math.round(Math.random() * 100); if (foo > 50) { alert("You are lucky!"); } else { alert("You are not lucky!"); } } // calling this function! areYouLucky(); |
function variation 2 – anonymous function expression, execute it via reference
1 2 3 4 5 6 7 8 9 10 11 12 |
// anonymous function #1 var isLucky = function() { var foo = Math.round(Math.random() * 100); if (foo > 50) { return "You are lucky!"; } else { return "You are not lucky!"; } }; var me = isLucky(); |
function variation 3 – Immediately Invoked Function Expression
say you have a function you want to execute like so:
1 2 3 4 |
function() { var shout = "I AM ALIVE!!!"; alert(shout); } |
…usually, you’ll have a variable name reference the function, then execute it. Or you give the function a name, and use that name for the execution as shown above.
You can also invoke a function like so:
1 2 3 4 5 |
var greeting = function() { return "hello"; }(); console.log(greeting); // hello |
What we just did is create a reference and point it to the return value of a function expression. Remember, a function without a name is a function expression. A function with a name is a function statement.
If you want to write function() as an expression itself, without a variable reference pointing to it, the compiler will give you an error.
1 2 3 4 |
// syntax error function() { } |
It’s expecting it to be a function statement. It’s looking for a function name. In order to remedy this, you can use parentheses. Also, when you use parenthesis, JS assumes that the inside is an expression. We can create a function expression and immediately invoke it like so:
1 2 3 4 |
(function() { var shout = "I AM ALIVE!!!"; console.log(shout); })(); |
The general structure for creating an IIFE that takes arguments is as follows:
1 2 3 4 5 6 7 8 |
// arg1 and arg2 somewhere in outer scope (function(a, b) { // a = ... // b = ... })(arg1, arg2); |
You can invoke it outside or inside the parentheses, doesn’t matter.
example:
1 2 3 4 5 6 7 8 9 |
(function(first, last) { console.log("My name is " + last + ", " + first + " " + last + "."); }("James", "Bond")); // same (function(first, last) { console.log("My name is " + last + ", " + first + " " + last + "."); })("James", "Bond"); |
What happens in memory
When the IIFE is immediately invoked, we have an execution context pushed onto the execution stack.
In its Creation Phase, it will put variables into memory.
1 2 3 |
(function(name) { var greeting = 'Hello'; }('John')); |
In our example, greeting variable is put into memory and assigned undefined. By definition, any variable created within a certain execution context belongs there. In other words, the variable is local to the function which is was defined. Furthermore, a let variable is local to the block which it was defined.
Sa you want to manipulate the global variable. Simple, just pass it in.
1 2 3 |
(function(global, name) { global.greeting = 'Hello'; }(window, 'John')); |
This pattern makes it intentional for you to affect the global object. And not by accidental.
Review Scope real quick…
…you learned that JavaScript doesn’t have the concept of block scoping. It only has lexical scope.
This means that variables declared inside a block such as an if statement or loop will actually be accessible to the entire enclosing function:
1 2 3 4 5 6 7 8 |
function scopeDemo() { if (true) { var foo = "I know what you did last summer!"; } alert(foo); // totally exists! } scopeDemo(); |
As you can see in this example, the foo variable, despite being stuck inside the if statement, is accessible outside of it because your if is not a scopable block. This ability to have your “supposedly inner” variables promoted and accessible to the entire enclosing function is known as variable hoisting!
When do use an IIFE?
what is so different about an IIFE compared to a function you declare and call immediately like you have always done?
The main difference is that an IIFE leaves behind no evidence of its existence after it has run.
This is largely because IIFEs are anonymous functions that are nameless. This means you can’t track them by examining variables. Because the code you put inside an IIFE is actually inside a function, any variables you declare are local to that function.
IIFE provides you with a very simple way of running code fully inside its own bubble and then disappearing without a trace.
By taking advantage of the local scope IIFE’s create, you can selectively choose what to expose or what not to expose.
Here, we declare an object. However, property secretCode is exposed for public usage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var weakSauce = { secretCode: "Zorb!", checkCode: function (code) { if (this.secretCode == code) { alert("You are awesome!"); } else { alert("Try again!"); } } }; var bar = Object.create(weakSauce); console.log(bar.secretCode) // exposed! |
By taking advantage of the local scope IIFE’s create, you can selectively choose what to expose or what not to expose
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var awesomeSauce = (function () { var secretCode = "Zorb!"; function privateCheckCode(code) { if (secretCode == code) { alert("You are awesome!"); } else { alert("Try again!"); } } // the public method we want to return return { checkCode: privateCheckCode }; })(); var foo = Object.create(awesomeSauce); alert(foo.secretCode); // undefined |
This time around, you won’t be able to access secretCode. The reason is that the contents of secretCode are buried inside the IIFE and not accessible publicly.
Usage: To Avoid Polluting the Global Scope
Let’s say you’re implementing your own objects. You have different function interfaces like so…
rtScript.js
1 2 3 4 5 |
var gCounter = 0; function toString() { ...} function add() { ... } |
The problem is, when you import your file into projects, what if other implementations have the same names as well? It would lead to name collisions.
So in order to solve this problem, we want to hide these function interfaces and just expose a global variable name like so:
1 2 3 4 5 6 7 |
var rtScript = (function() { var gCounter = 0; return { toString: function {...} add: function{...} } })(); |
What this does is that it hides all the function interfaces and global variables used in your implementation.
All that’s exposed is the rtScript variable.
Hence others can use your implementation like so:
otherCode.js
1 2 3 4 5 6 |
(function(rt) { rt.toString(); rt.add(); }(rtScript)); |
Usage: capture surrounding state via Closure, and parameter passing
Given we have a function quotatious
It takes an array, and pushes a inner function onto it. Thus, it manipulates the references in quotes and points them to their own respective inner function. Even though the scope have not been created just yet, each inner function references its surround environment. Thus, they all reference the i from the loop and the array names.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function quotatious(names) { var quotes = []; for (var i = 0; i < names.length; i++) { var theFunction = function() { return "My name is " + names[i] + "!"; } quotes.push(theFunction); } return quotes; } |
In the outer scope, when we declare people to be an array with let’s say 4 elements, and pass it into quotatious, we will see that quotes array inside quotatious will have its references pointing to inner functions.
Each inner function will reference i as well as name.
Then quotes array will be returned by quotations function.
We then execute its element, which is a reference to the inner function.
1 2 3 4 5 6 7 8 |
// our list of names var people = ["Tony Stark", "John Rambo", "James Bond", "Rick James"]; // getting an array of functions var peopleFunctions = quotatious(people); // get the first function peopleFunctions[0](); |
When we execute the 0th element of peopleFunction, it will execute the inner function. The inner function will create scope, and reference i. But at this point, i is 5 because it shared among all the inner functions in quotatious.
Thus, names[5] returns undefined.
Solution
The trick is to create a shell that holds the correct i for each inner function.
Hence we create an IIFE first. the IIFE will take in the i as a parameter, and copy the value into the parameter index.
names is captured by closure, where as i is captured by parameter passing.
1 2 3 4 5 |
for (var i = 0; i < names.length; i++) { (function(index) { // IIFE })(i); } |
This is the key here. As different i gets passed into the IIFE, the i is kept as that value via the parameter index. Hence any inner function in the IIFE will always be able to reference index as that value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
for (var i = 0; i < names.length; i++) { (function(index) { // IIFE var temp = function() { // inner function return names[index]; // green dotted line } quotes.push(temp); // black line })(i); } return quotes; |
So when we use quotes to execute the inner function, the inner function will be able to get its own referenced i.
1 |
quotes[0](); // purple arrow |