7

I am an absolute newbie, and I just read this in JavaScript: The Good Parts.

In the chapter talking about scope, it says "It is important to understand that the inner function has access to the actual variables of the outer functions and not copies in order to avoid the following problem." And then the two following examples look like this:

//BAD EXAMPLE

var add_the_handlers = function (nodes) {
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function (e) {
            alert(i);
        };
    }
 };

//END BAD EXAMPLE

var add_the_handlers = function (nodes) {
    var helper = function (i) {
        return function (e) {
            alert(i);
        };
    };
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        modes[i].onclick = helper(i);
    }
};

According to the author the second example is better because it doesn't use a loop inside the function, otherwise it could be wasteful computationally. But I am at loss and don't know what to do with them. How do I put his theory in real application? Can anyone illustrate these two examples combine HTML?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
HanChen
  • 71
  • 2
  • 2
    Important to understand that when event occurs .... `i` will not be what you want it to be See http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – charlietfl Jan 18 '17 at 03:49
  • What do you mean by "two examples combine HTML"? – Peter Mortensen Jan 18 '17 at 09:01

5 Answers5

6

The problem is with closure. The inner functions have access to the variable i defined outside of these functions. After all iterations of the loop have been executed, the variable i will hold the value of nodes.length. So when you click on nodes[0], the alert will say nodes.length, which is not what you'd expect. (You would expect the alert to say 0.) The same holds when you click on nodes[1], nodes[2], etc. The alert for all of them will say nodes.length.

Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
Kevin Le - Khnle
  • 10,579
  • 11
  • 54
  • 80
  • Actually, the value of i shown in the alert will be `nodes.length`, not `nodes.length - 1`, since the final value of a loop variable is the first value for which the condition fails, not the last value for which the condition passes. This is indeed what Crockford was talking about - if you were to run the "bad code", and you had elements 0 through 4, all of the alerts would say "5" because they all share the same variable, which is contained in five different closures all referencing the same scope. – PMV Jan 18 '17 at 04:20
  • Yes, you're right. Off by 1 problem in my answer. – Kevin Le - Khnle Jan 18 '17 at 04:22
2

Firstly, in the bad example, a function is created for each event handler; the loop creates multiple function objects. Whereas in the second example, a single function is created and referenced from inside the loop. So you save a lot of memory.

Secondly, in the bad example, as the value of "i" runs, the function does not retain the value, and when it runs, it will always return the last value of "i". In the good example however, as "i" is passed into the function, this value is retained as the lexical environment of the function, and when it is called, it will return the correct value.

Thirdly, as mentioned by @Gary Hayes, we might want to use the function elsewhere too. So it's best to keep it independent of the loop.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
poushy
  • 1,114
  • 11
  • 17
  • 1
    Good answer. Also, you may want to run that function somewhere else, without having to run the loop. – Gary Hayes Jan 18 '17 at 04:34
  • Yes very true @GaryHayes ... Please do feel free to edit my answer and add in this excellent point! – poushy Jan 18 '17 at 04:43
  • Same problem as previous answer: as many click functions are created as there are nodes in both the good and bad examples. – traktor Jan 18 '17 at 07:19
  • @Traktor53 - I'm sorry but that is incorrect. That is the difference between function object and the instance of the memory in which the function will run, I.e. the lexical scope. Both are different. In second case function object is not created in the loop, but lexical scope is created for each handler, which helps maintain the values of "i". – poushy Jan 18 '17 at 07:29
  • @poushy The return statement in `helper` returns a function expression which is evaluated each time the return statement is executed. Each evaluation results in a different function object being created and returned. `helper(1) != helper(2)` is easily demonstrated. – traktor Jan 18 '17 at 07:41
  • @Traktor53 - It's a reference to the lexical scope which encases the variable states at that point of time and reference to the function it returned. Both are different as they will return different states on each iteration. Please refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures – poushy Jan 18 '17 at 07:48
1

You can check it with HTML working here: https://jsfiddle.net/vdrr4519/.

'multifunc' elements are inited with example with many functions, 'singlefunc'—with a single one. See, we take all the elements with a class and pass them to the function.

multifunc(document.querySelectorAll('.multifunc'));

Function runs 'for' loop and adds 'click' event listener. So the element should alert its index on click (beginning from 0). But in example with many function a wrong value is produced (because of closure, other answers also highlight the issue).

I think I should say also that it's not issue of single function/mutliple functions—it's a question of working with closures. You see, I can implement a working example WITH many closures: https://jsfiddle.net/pr7gqtdr/1/. I do basically the same thing that you do in a single-function handler, but every time call the new 'helper' function:

 nodes[i].onclick = function (i) {
    return function (e) {
        alert(i);
    };
 }(i); 

See, this (i) at the end is an immediate function call, so onclick gets a function with i variable set in closure.

But, the single function options is a bit better, because it's more memory efficient, I guess. Functions are objects. If you create many of them, you take more memory, in general. So, choosing from these, I'd stick with 'handler' function option.

everyonesdesign
  • 356
  • 2
  • 9
0

The bad example creates a lot of event handlers; One per event. The good example create a single event handler and assigns it to all the events.

With the bad example, you've created lots of separate functions, instead of just one. That can be a lot of extra overhead and a lot of potential scope problems. These include closure issues such as an event only firing for the last item in the loop.

Additionally, the good example allows you to more easily unsubscribe the events because you have access to the original function pointer.

The good example is also just easier to read and understand. The loop is only used for creating the elements and binding their events; The handling of those events is done elsewhere.

Soviut
  • 88,194
  • 49
  • 192
  • 260
  • 2
    Also you'll typically run into the "why does my event only fire for the last value of i" closure problems. – Evan Trimboli Jan 18 '17 at 03:57
  • Thanks, I've included that in the answer. – Soviut Jan 18 '17 at 04:28
  • 2
    I don't think so. The good example creates a separate, anonymous function for each node element, inside `helper` to capture `i` in a closure, and attaches the returned function as an `onclick` attribute value. `helper` could not be used to remove an event listener added with `addEventListener`. – traktor Jan 18 '17 at 07:03
  • 1
    "The good example create a single event handler and assigns it to all the events." No it doesn't. It creates a unique function and adds that to each node's onclick property. – curiousdannii Jan 18 '17 at 08:15
  • @curiousdannii the function is the event handler and the onclick property is one way of binding that function to the click event. – Soviut Jan 18 '17 at 09:42
0

As Soviut mentions, you are creating lots of event handlers in the bad example. Moreover, it is important to point out that the bad example functions refer to the same i variable, which means all of them will have the last value of nodes.length when they execute.

This is because a closure is created. You can read more about it in Closures.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ghasan غسان
  • 5,577
  • 4
  • 33
  • 44
  • Both examples create a separate function for each event handler. The good example is no more efficient than the bad one; it does, however, correctly capture the *current* value of the `i` variable inside each closure. – Ilmari Karonen Jan 18 '17 at 08:42