If you try this code out, you’ll see that it doesn’t work as expected. No matter what field you focus on, the message about your age will be displayed.
The reason for this is that the functions assigned to onfocus
are closures; they consist of the function definition and the captured environment from the setupHelp
function’s scope.
Three closures have been created by the loop, but each one shares the same single lexical environment, which has a variable with changing values (
item
). This is because the variableitem
is declared withvar
and thus has function scope due to hoisting. The value ofitem.help
is determined when theonfocus
callbacks are executed. Because the loop has already run its course by that time, theitem
variable object (shared by all three closures) has been left pointing to the last entry in thehelpText
list.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Asynchronus</title>
</head>
<body>
<div class="root"></div>
<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>
<script>
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function helperMaker(help) {
return function () {
showHelp(help);
console.dir(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 = helperMaker(item.help)
}
}
setupHelp();
</script>
</body>
</html>