4

I am having trouble with JS closures:

// arg: an array of strings. each string is a mentioned user.
// fills in the list of mentioned users. Click on a mentioned user's name causes the page to load that user's info.
function fillInMentioned(mentions) {
    var mentionList = document.getElementById("mention-list");
    mentionList.innerHTML = "";
    for (var i = 0; i < mentions.length; i++) {
        var newAnchor = document.createElement("a");

        // cause the page to load info for this screen name
        newAnchor.onclick = function () { loadUsernameInfo(mentions[i]) };

        // give this anchor the necessary content
        newAnchor.innerHTML = mentions[i];

        var newListItem = document.createElement("li");
        newListItem.appendChild(newAnchor);
        mentionList.appendChild(newListItem);
    }
    document.getElementById("mentions").setAttribute("class", ""); // unhide. hacky hack hack.
}

Unfortunately, clicking on one of these anchor tags results in a call like this:

loadUserNameInfo(undefined);

Why is this? My goal is an anchor like this:

<a onclick="loadUserNameInfo(someguy)">someguy</a>

How can I produce this?

Update This works:

newAnchor.onclick = function () { loadUsernameInfo(this.innerHTML) };
newAnchor.innerHTML = mentions[i];
Nick Heiner
  • 119,074
  • 188
  • 476
  • 699
  • 5
    This is one of the most common and repetitive questions here regarding closures, possible dupes: http://stackoverflow.com/questions/1734749/ http://stackoverflow.com/questions/643542/ http://stackoverflow.com/questions/1582634/ http://stackoverflow.com/questions/1331769/ http://stackoverflow.com/questions/1552941/ http://stackoverflow.com/questions/750486/ http://stackoverflow.com/questions/933343/ http://stackoverflow.com/questions/1579978/ http://stackoverflow.com/questions/1413916/ – Christian C. Salvadó Feb 22 '10 at 21:16

2 Answers2

7

The "i" reference inside the closure for the onclick handlers is trapping a live reference to "i". It gets updated for every loop, which affects all the closures created so far as well. When your while loop ends, "i" is just past the end of the mentions array, so mentions[i] == undefined for all of them.

Do this:

newAnchor.onclick = (function(idx) {
    return function () { loadUsernameInfo(mentions[idx]) };
})(i);

to force the "i" to lock into a value idx inside the closure.

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
2

Your iterator i is stored as a reference, not as a value and so, as it is changed outside the closure, all the references to it are changing.

try this

function fillInMentioned(mentions) { 
    var mentionList = document.getElementById("mention-list"); 
    mentionList.innerHTML = ""; 
    for (var i = 0; i < mentions.length; i++) { 
        var newAnchor = document.createElement("a"); 

        // Set the index as a property of the object 
        newAnchor.idx = i;
        newAnchor.onclick = function () { 
            // Now use the property of the current object
            loadUsernameInfo(mentions[this.idx]) 
        }; 

        // give this anchor the necessary content 
        newAnchor.innerHTML = mentions[i]; 

        var newListItem = document.createElement("li"); 
        newListItem.appendChild(newAnchor); 
        mentionList.appendChild(newListItem); 
    } 
    document.getElementById("mentions").setAttribute("class", "");  
} 
Greg B
  • 14,597
  • 18
  • 87
  • 141