Function - Javascript closure scope

De openkb
Aller à : Navigation, rechercher

Sommaire

Questions

I was working today at a front-end Javascript project. I will try to keep the description of the problem and the solution as short as possible. I had to add click handlers to the links on a page that redirect the user to other pages, so I had 2 Javascript array arrayOfRedirectLinks and pageLinkElements:

var arrayOfRedirectLinks, pageLinkElements;

Initially I wrote the addEventHandlers function like this:

var addEventHandlers = function() {
    var i, link;
    for( var i in arrayOfRedirectLinks) {
        link = arrayOfRedirectLinks[i];
        pageLinkElements[i].addEventListener( click , function(e) {
            e.preventDefault();
            window.location = link;
        });
    }
}

I thought that this solution will do the job until... well until I opened the browser, clicked several links and noticed that all of them redirected me to the same link( the last link in the arrayOfRedirectLinks).

Finally I found that my problem was similar to the one posted here http://stackoverflow.com/questions/14177757/javascript-multiple-dynamic-addeventlistener-created-in-for-loop-passing-param http://stackoverflow.com/questions/14177757/javascript-multiple-dynamic-addeventlistener-created-in-for-loop-passing-param

And indeed both the first and the second solution posted there worked for me

var addEventHandlers = function() {
    var i, link;
    for( var i in arrayOfRedirectLinks) {
        (function(link){
        link = arrayOfRedirectLinks[i];
        pageLinkElements[i].addEventListener( click , function(e) {
            e.preventDefault();
            window.location = link;
        });
        }(link));
    }
}

and

var passLink = function(link) {
    return  function(e) {
               e.preventDefault();
               window.location = link;
            };
};
var addEventHandlers = function() {
    var i, link;
    for( var i in arrayOfRedirectLinks) {
        link = arrayOfRedirectLinks[i];
        pageLinkElements[i].addEventListener( click ,passLink(link));
    }
}

Now this seems to work but I don t understand why it works. I came with the following explanation and I would like if someone can confirm if it s correct:

1) When I declare a function in Javascript, it gets the references to the variables in the scope of the function where it was declared. ( i.e. my event handler gets a reference to the link variable in the addEventHandlers function)

2) Because the handler gets a reference to the variable link. When I reassign a value to the link variable, the value that will be used when the click handler gets triggered will also change. So the link variable from the event handler is not simply copy with of the link with a different memory address and same value as when the function handler was added, but they both share the same memory address and therefore the same value.

3) Because of the reasons described at 2), the all the click handlers will use the redirect to the same link, the last link in the array arrayOfRedirectLinks because that s the last value that will get assigned to the link variable at the end of the for loop.

4) But when I pass the link variable as a parameter to another function, a new scope it s created and the link inside that scope actually shares only it s initial value with the value of the link parameter passed to the function. The references of the 2 link variables are different.

5) Because of 4), when I pass the link to the click handler, it will take the reference to the link variable in the Immediately Invoked Function Expression who itself doesn t share the same address with the link in the addEventHandlers function. Therefore each link from the event handler functions will be isolated from the others and will keep the value of the arrayOfRedirectLinks[i]

Is this correct?

Answers

This is the critical bit:

var i, link;
for( var i in arrayOfRedirectLinks) {
  link = arrayOfRedirectLinks[i];
  pageLinkElements[i].addEventListener( click , function(e) {
    e.preventDefault();
    window.location = link;
  });
}

There is only ever one link variable here. Note that the addEventListener callback function is only called when the link is clicked.

By that time, the variable link has its final value, which is shared by all event handler functions.

So all the links do the same thing.

Simplest solution (other than a wider refactor):

for(var i in arrayOfRedirectLinks) {      
  (function(i) {
    var link = arrayOfRedirectLinks[i];
    pageLinkElements[i].addEventListener( click , function(e) {
      e.preventDefault();
      window.location = link;
    });
  }(i));
}

Source

License : cc by-sa 3.0

http://stackoverflow.com/questions/34053005/javascript-closure-scope

Related

Outils personnels
Espaces de noms

Variantes
Actions
Navigation
Outils