views:

662

answers:

4

Hi All:

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 bind 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 codes 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. 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?

thanks.

A: 

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.

Zed
+3  A: 

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);
    }
}
Christoph
+1  A: 

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.

Imagist
+2  A: 

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.

nlogax
mm, ive never seen a solution that used the with statement before, nice ;)
meder