0

I am learning about closures and have the basics on what they are and how they work.

I got the following code from MDN and know what the solution is since it's in the same article. I just don't get how this is possible:

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>


function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}


function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

I know the section of code that needs change for this to work is:

document.getElementById(item.id).onfocus = function() {

      showHelp(item.help);

    }

How is it that at the end of the loop the text being pointed to is: Your age (you must be over 16) for all elements?

I can follow the code and see that the loop successfully loops through the elements correctly but I can't get my head around how is it that the last item pointed to for all elements at the end is Your age ... since it saves each one individually with the onfocus = funtion()... and what ever item.help is at the time is passed in and saved.

Any step by step explanation would greatly help in me understanding what is going on.

Robert
  • 10,126
  • 19
  • 78
  • 130
  • See also this answer for a longer discussion of what's going on: http://stackoverflow.com/questions/3572480/please-explain-the-use-of-javascript-closures-in-loops/3572616#3572616 – slebetman Jul 02 '15 at 02:20

2 Answers2

1

JavaScript has functional scope. That means all variables defined in a function are hoisted to the top of the function. C# or Java has block level scope so defining item in a loop is the natural way in those languages . That is not the case in JavaScript. To avoid confusion all variables should be declared at the top of the function. The JS interpreter will hoist var item to the top of the function when it evaluates it. In the loop you are setting onfocus to a function with a variable referencing item in the the parent function scope of setupHelp. At the end of the loop, item has the assigned value of the last indexed item. So every time onfocus executes the function it references the assigned item value in the setupHelp function scope. In order to create a closure you will need to execute an anonymous function in each iteration of the loop that will create a new functional scope. This new scope should have a var that is assigned the current item from the outer function.

This example shows a functional scope closure in each iteration of the for loop:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}


function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = (function() {
        var saveItem = item;
        return function () {
            showHelp(saveItem.help);
        }
    })();
  }
}

setupHelp();

Example fiddle:

https://jsfiddle.net/ohoy75kh/1/

Or

https://jsfiddle.net/ohoy75kh/2/

phil-code
  • 26
  • 2
0

If I understand you correctly, you're asking why does the code point to Your age (you must be over 16) any time you focus on an element?

This isn't really a closures questions, but a problem with the loop is finding and setting the text to be helped.

So, every time you click on an input, the function runs, right? The function loops through every member of the array, 3 items every time, and on each iteration it sets the help variable to the current array item. But there's nothing to stop the loop and every time it runs it runs completely through the array. So it always stops at the end, which means that help is always set to the last variable. It'll never stop anywhere else, so whenever you click on an element, the process is always like this.

  1. Click on Element (doesn't matter which one)
  2. JavaScript starts the loop.
  3. The loop iterates through to the first element in the array and sets the item variable to the first element.
  4. The loop iterates through to the second element in the array and sets the item variable to the second element.
  5. The loop iterates through to the third element in the array and sets the item variable to the third element.
  6. JavaScript sets the innerHTML of the p#id element to the text of the item variable's "help" property.

So it's always showing for the third element because it's always stopping at the third element.

whatoncewaslost
  • 2,216
  • 2
  • 17
  • 25
  • I don't think so. The loop assigns a function (handler) for each `onfoucs` event of the element. The loop runs once. – Robert Jul 02 '15 at 02:06
  • That's what I'm saying, the loop runs only once once, but because the function `document.getElementById(item.id).onfocus = function() { showHelp(item.help); } ` is contained in the loop it runs three times and each time resets the innerHTML of the same p#help element. So at the end, the p#id element is left with the very last element the loop selected, the "Age" one. – whatoncewaslost Jul 02 '15 at 02:08