Skip to content Skip to sidebar Skip to footer

Effective Javascript Book Having Trouble Understanding Item 13(iffy)

Trying to read this wonderful book of effective javascript(but I am still novice).. In item 13, it talks about immediately invoked function expression to create local scopes. I unf

Solution 1:

Let me see if I can help you understand a little. The concept of Immediately-Invoked Function Expressions or (the popular term back when I learned them) Self-Executing Anonymous Functions can be a difficult one to grasp and I recommend that you have a solid knowledge of JavaScript before really digging in, but I may be able to help explain it to you in a way that will help you understand.

So - let's start by examining a normal function declaration that can be done one of two ways:

functionsomeFunction(param1, param2) {
    //do stuff
};

OR

var someFunction = function (param1, param2) {
    //do stuff
};

And to invoke this function, you would call it like so:

someFunction("param1value", "param2value");

Which is great and works exactly how it's supposed to. But what if you need a function that executes or invokes immediately when it's ran, and don't want to add an object to the global namespace? Here's the real benefit of an IIFE (SEAF). Here is the basic structure of an anonymous function:

(function () {

})();

The first set of parenthesis after (function () { pass parameters into the scope of the function. The second set of parenthesis })(); are used to invoke the function and pass parameters into the function. Wrapping the function declaration in parenthesis makes the function anonymous and allows it to execute or invoke immediately.

Let's take a very basic beginning framework example and explain in a little more detail:

(function (window, undefined) {

})(window);

I remember when I first saw this, I couldn't figure what was going on here and what the point was... This function accepts two parameters to pass into the scope of the function, a window object and an undefined object. (function (window, undefined) { Then when we call it, we pass in only one window object (the global window scope). })(window); To help you understand, this would be like writing a function and executing it like this:

functiondoStuff (window, undefined) {
    //do stuff here
};
doStuff(window);

So why don't people just write their code this way instead of worrying about these IIFE's? Well, writing your functions this way, could clog up your global scope, meaning that now you have a doStuff() object defined that is available across the scope of your entire project. If you have a really large project or framework, you typically only want to expose one object to the global scope, and keep everything else anonymous so it doesn't overwrite or get overwritten by additional code that may also be included in the application.

This is really the basics, but to help you understand the syntax a little more, let me do a real basic working example for you, just to help you wrap your head around the concept. In this example, as soon as the code runs, we're just going to multiply two numbers, whichever two numbers you pass into the function. Then we're going to output the result to a text box with the id "result". You can play around with it here: http://jsfiddle.net/k7f4n0mk/

(function (number1, number2) {
    document.getElementById("result").value = (number1 * number2);
})(5, 10);

If we were to write this without an IIFE, you would first have to define the function, then invoke it and it would look like this:

functionmultiply(number1, number2) {
    document.getElementById("result").value = (number1 * number2);
};
multiply(5, 10);

You can see this example here: http://jsfiddle.net/k7f4n0mk/1/

Both examples produce the exact same result, and I'm assuming that you're fairly comfortable with the second one since it's one of the very basics of the language, so why should you worry about this whole new way to write a function if you're old way works just fine? Well, again, this goes back to keeping the global scope clean, and protecting the local scope.

Everyone is pretty familiar with the jQuery javascript library. The entire context of jQuery is wrapped in an IIFE and only the jQuery and the alias $ object are exposed to the global scope - that's pretty impressive for everything that jQuery does. Well, if jQuery didn't have this IIFE syntax, then every function that they declared would be available to the global scope and could easily be overwritten by an unknowing user. They could overwrite any of the function that jQuery uses and completely break the library - so we want to protect all of the functions that jQuery uses (local scope) and keep the global scope clean by only exposing the necessary objects (jQuery and $).

I know this has been a really long answer, but I hope that I have been able to help you gain a little more understanding on this subject. Let me know if you have any other questions.

-EDIT-

To the point of your question - let me see if I can help explain in greater detail.

Here is the code that you are using:

functionwrapElements(a) {
    var result = [], i, n;
    for (i = 0, n = a.length; i < n; i++) {
        result[i] = function () { return a[i]; };
    }
    return result;
}

var wrapped = wrapElements([10, 20, 30, 40, 50]);
var f = wrapped[0];
f();

Now, when you call var wrapped = wrapElements([10, 20, 30, 40, 50]);

wrapped is now referencing an array of functions, because that's what you're returning in your for loop:

wrapped = [function () { return a[i]; },function () { return a[i]; },function () { return a[i]; },function () { return a[i]; },function () { return a[i]; }]

Then, when you call var f = wrapped[0], f becomes a reference to the first function in the array

f = function () { return a[i]; }

So, what you are doing in this code, is adding a new function to the array in your loop. If you try to call the function, a and i will be undefined which is why you are receiving an undefined error.

To achieve the desired results, the code would look like this:

functionwrapElements(a) {
    var result = [], i, n;
    for (i = 0, n = a.length; i < n; i++) {
        result[i] = a[i];
    }
    return result;
}

var wrapped = wrapElements([10, 20, 30, 40, 50]);
var f = wrapped[0];

I hope this helps your understanding a little more. Please let me know if you need any further assistance.

Solution 2:

This is a very common bug. Let me simplify it for you.

The following program should alert 1, 2 and 3:

for (var i = 1; i <= 3; i++) {
  setTimeout(function () {
    alert(i);
  }, 10);
}

However, as you can see it alerts 4 3 times instead. What's happening is that by the time the function given to setTimeout is called the value of i has changed. That's the same problem that you are facing.

To solve this problem we make use of immediately invoked function expressions (IIFEs).

for (var i = 1; i <=3; i++) {
  (function (new_i) {
    setTimeout(function () {
      alert(new_i);
    }, 10);
  }(i));
}

By using an IIFE we're creating a new variable called i whose value is the that of the old i. Now when the old i changes, the new i remains the same. Hence we get the expected result.

A better way to write this would be to use the with statement to create a new i as follows:

for (var i = 1; i <= 3; i++) {
  with ({ new_i: i }) {
    setTimeout(function () {
      alert(new_i);
    }, 10);
  }
}

Using with is better than the IIFE for 2 reasons:

  1. It's cleaner and easier to read and understand.
  2. It's faster because no function is being invoked. Function invocation is slow.

Hope that helps.

Post a Comment for "Effective Javascript Book Having Trouble Understanding Item 13(iffy)"