Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript closures and variable scope

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];
like image 651
Nick Heiner Avatar asked Jan 23 '23 12:01

Nick Heiner


2 Answers

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.

like image 54
Ben Zotto Avatar answered Feb 01 '23 16:02

Ben Zotto


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", "");  
} 
like image 34
Greg B Avatar answered Feb 01 '23 16:02

Greg B