Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Scoping rules for Javascript can create unexpected impacts with loops and closuresSo consider this problem. Let’s say you are constructing a menu and you need to set up some onclick events for the items in the menu.

...

So this is how one might set things up from first principles:

Code Block
languagejs
// Set up an array of items.
var MENUitems = ["Company", "Product", "History", "Contact"];

var H = '';
for (var i=0; i < MENUitems.length; i++) { 
   H+= "<div class='MENUitem'>" + MENUitems[i] + "</div>" 
}
document.body.innerHTML = H;

And you might have some light styling:

Code Block
languagecss
.MENUitem {
    display: inline-block;
    padding: 5px;
    border: gray solid;
    margin: 2px;
}

Then you set up some onclick handlers…

Code Block
languagejs
var MENUdom = document.querySelectorAll(".MENUitem");  // Get a NodeList
for (var i=0; i < MENUitems.length; i++) {
   MENUdom[i].onclick = function(E){ console.log("Clicked " + MENUitems[i]);}
}

But oh crap.

It’s doesn’t work as expected. Instead of forming a closure with the value i being 0, 1, 2 and 3, all the functions get the value of i defined as it is at the end of the loop - namely 4. And so we end up with:

Code Block
Clicked undefined

A fix for this is to use a special keyword which is “let” instead of var. So you do this instead:

Code Block
languagejs
for (let i=0; i < MENUitems.length; i++) {
   MENUdom[i].onclick = function(E){ console.log("Clicked " + MENUitems[i]);}
}

It’s kind of hacky workaround a language flaw in Javascript. Using the let keyword isn’t the only way to solve this problem. There are other workarounds possible. Read this article:

https://davidyoung.tech/loops-closures-and-ajax

For some other good ideas on how to solve this problem.

This video also touches on the concept of NodeList objects.