Javascript infamous Loop issue?

JavascriptClosures

Javascript Problem Overview


I've got the following code snippet.

function addLinks () {
    for (var i=0, link; i<5; i++) {
        link = document.createElement("a");
        link.innerHTML = "Link " + i;
        link.onclick = function () {
            alert(i);
        };
        document.body.appendChild(link);
    }
}

The above code is for generating 5 links and binding each link with an alert event to show the current link id. But It doesn't work. When you click the generated links they all say "link 5".

But the following code snippet works as our expectation.

function addLinks () {
    for (var i=0, link; i<5; i++) {
        link = document.createElement("a");
        link.innerHTML = "Link " + i;
        link.onclick = function (num) {
            return function () {
                alert(num);
            };
        }(i);
        document.body.appendChild(link);
    }
}

The above 2 snippets are quoted from [here][1]. As the author's explanation seems the closure makes the magic.

But how it works and How closure makes it work are all beyond my understanding. Why the first one doesn't work while the second one works? Can anyone give a detailed explanation about the magic?

[1]: http://robertnyman.com/2008/10/09/explaining-javascript-scope-and-closures/ "here"

Javascript Solutions


Solution 1 - Javascript

Quoting myself for an explanation of the first example:

> JavaScript's scopes are function-level, not block-level, and creating a closure just means that the enclosing scope gets added to the lexical environment of the enclosed function. > > After the loop terminates, the function-level variable i has the value 5, and that's what the inner function 'sees'.

In the second example, for each iteration step the outer function literal will evaluate to a new function object with its own scope and local variable num, whose value is set to the current value of i. As num is never modified, it will stay constant over the lifetime of the closure: The next iteration step doesn't overwrite the old value as the function objects are independant.

Keep in mind that this approach is rather inefficient as two new function objects have to be created for each link. This is unnecessary, as they can easily be shared if you use the DOM node for information storage:

function linkListener() {
    alert(this.i);
}

function addLinks () {
    for(var i = 0; i < 5; ++i) {
        var link = document.createElement('a');
        link.appendChild(document.createTextNode('Link ' + i));
        link.i = i;
        link.onclick = linkListener;
        document.body.appendChild(link);
    }
}

Solution 2 - Javascript

We have 5 divs on the page, each with an ID ... div1, div2, div3, div4, div5

jQuery can do this ...

for (var i=1; i<=5; i++) {
    $("#div" + i).click ( function() { alert ($(this).index()) } )
}

But really addressing the problem (and building this up slowly) ...

STEP 1
for (var i=1; i<=5; i++) {
    $("#div" + i).click (
        // TODO: Write function to handle click event
    )
}
STEP 2
for (var i=1; i<=5; i++) {
    $("#div" + i).click (
        function(num) {
            // A functions variable values are set WHEN THE FUNCTION IS CALLED!
            // PLEASE UNDERSTAND THIS AND YOU ARE HOME AND DRY (took me 2 years)!
            // Now the click event is expecting a function as a handler so return it
            return function() { alert (num) }
        }(i) // We call the function here, passing in i
    )
}
SIMPLE TO UNDERSTAND ALTERNATIVE

If you can't get your head around that then this should be easier to understand and has the same effect ...

for (var i=1; i<=5; i++) {

	function clickHandler(num) {	
		$("#div" + i).click (
			function() { alert (num) }
		)
	}
	clickHandler(i);
	
}

This should be simple to understand if you remember that a functions variable values are set when the function is called (but this uses the exact same thought process as before)

Solution 3 - Javascript

Basically, in the first example you're binding the i inside the onclick handler directly to the i outside the onclick handler. So when the i outside the onclick handler changes, the i inside the onclick handler changes too.

In the second example, instead of binding it to the num in the onclick handler, you're passing it into a function, which then binds it to the num in the onclick handler. When you pass it into the function, the value of i is copied, not bound to num. So when i changes, num stays the same. The copy occurs because functions in JavaScript are "closures", meaning that once something is passed into the function, it's "closed" for outside modification.

Solution 4 - Javascript

Others have explained what's going on, here's an alternative solution.

function addLinks () {
  for (var i = 0, link; i < 5; i++) {
    link = document.createElement("a");
    link.innerHTML = "Link " + i;

    with ({ n: i }) {
      link.onclick = function() {
        alert(n);
      };
    }
    document.body.appendChild(link);
  }
}

Basically the poor mans let-binding.

Solution 5 - Javascript

In the first example, you simply bind this function to the onclick event:

function() {alert(i);};

This means that on the click event js should alert the value of the addlink functions i variable. Its value will be 5 because of the for loop().

In the second example you generate a function to be bound with another function:

function (num) {
  return function () { alert(num); };
}

This means: if called with a value, return me a function that will alert the input value. E.g. calling function(3) will return function() { alert(3) };.

You call this function with the value i at every iteration, thus you create separate onclick functions for each links.

The point is that in the first example your function contained a variable reference, while in the second one with the help of the outer function you substituted the reference with an actual value. This is called a closure roughly because you "enclose" the current value of a variable within your function instead of keeping a reference to it.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZhu TaoView Question on Stackoverflow
Solution 1 - JavascriptChristophView Answer on Stackoverflow
Solution 2 - JavascriptDaniel LewisView Answer on Stackoverflow
Solution 3 - JavascriptImagistView Answer on Stackoverflow
Solution 4 - JavascriptnlogaxView Answer on Stackoverflow
Solution 5 - JavascriptZedView Answer on Stackoverflow