Reputation: 815
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 Javascript multiple dynamic addEventListener created in for loop - passing parameters not working
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:
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)
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.
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.
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.
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?
Upvotes: 2
Views: 98
Reputation: 55729
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));
}
Upvotes: 3