Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript Function inside the loop

Can someone explain to me why JSLint complains about "Function inside the loop" with this example:

  for (var i = 0; i < buttons.length; i++) {
    (function(i) {
      buttons[i].onclick = function(e) {
        t.progressBars[t.current].update(buttons[i].getAttribute("data-value"));
      }
    })(i);
  }

But dosen't when I change it to:

function makeHandler(i)
  {
    return function() {
        t.progressBars[t.current].update(buttons[i].getAttribute("data-value"));
      }
  }

  for (var i = 0; i < buttons.length; i++) {

      buttons[i].onclick = makeHandler(i);

  }

I don't quite understand as it seems that with each loop iteration new function object has to be returned, even though it happens inside of makeHandler() function. Why the second example is ok with JS linters?

like image 990
spirytus Avatar asked Dec 12 '22 03:12

spirytus


2 Answers

Quoting from linterrors,

var elems = document.getElementsByClassName("myClass"), i;
for (i = 0; i < elems.length; i++) {
    (function (iCopy) {
        "use strict";
         elems[i].addEventListener("click", function () {
            this.innerHTML = iCopy;
         });
    }(i));
}

What we have now captures the value of i at each iteration of the loop. This happens because JavaScript passes arguments to functions by value. This means that iCopy within the capturing function is not related to i in any way (except for the fact that they happen to have the same value at that point in time). If i changes later (which it does - on the next iteration of the loop) then iCopy is not affected.

This will work as we expect it to but the problem now is that the JavaScript interpreter will create an instance of the capturing function per loop iteration. It has to do this because it doesn't know if the function object will be modified elsewhere. Since functions are standard JavaScript objects, they can have properties like any other object, which could be changed in the loop. Thus by creating the function in the loop context, you cause the interpreter to create multiple function instances, which can cause unexpected behavior and performance problems. To fix the issue, we need to move the function out of the loop:

I would have liked to use Array.prototype.forEach here, like this

buttons.forEach(function(curButton) {
    curButton.onclick = function(e) {
        t.progressBars[t.current].update(curButton.getAttribute("data-value"));
    };
});
like image 180
thefourtheye Avatar answered Dec 28 '22 23:12

thefourtheye


Your two examples are not equivalent.

In the first, you are creating an anonymous function and calling it on every loop.

The inner function (the click event handler) is fine - you're assigning a new function - but it's the anonymous outer function that is inefficient in this context. In your second example the outer function is refactored out of the loop where is it only created once, instead of buttons.length times.

like image 44
Hamish Avatar answered Dec 29 '22 00:12

Hamish