技术频道导航
HTML/CSS
.NET技术
IIS技术
PHP技术
Js/JQuery
Photoshop
Fireworks
服务器技术
操作系统
网站运营

赞助商

分类目录

赞助商

最新文章

搜索

[5个示例]介绍在循环中创建闭包的常见错误及解决方法

作者:admin    时间:2022-6-3 13:12:23    浏览:

在循环中创建闭包,很容易出现问题,本文将通过5个示例,介绍在循环中创建闭包的常见错误,以及如何使用正确的方法。

介绍在循环中创建闭包的常见错误及解决方法

在循环中创建闭包的常见错误

在循环中有一个常见的闭包创建问题,我们先来看看例子。

完整HTML

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title></title>

</head>
<body>
<p id="help">点击输入框时,这里显示提示信息</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 type="text/javascript">

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

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': '你的Email'},
      {'id': 'name', 'help': '你的名字'},
      {'id': 'age', 'help': '你的年龄'}
    ];

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

setupHelp();

</script>

 

</body>
</html>

demodownload

示例的预期是,当点击输入框时,显示对应的提示信息。

不过执行结果却不符合预期,点击输入框时,提示信息并不跟着变化。无论焦点在哪个input上,显示的都是关于年龄的信息。

 

出现这个原因是在循环中使用的闭包有问题。

数组 helpText 中定义了三个有用的提示信息,每一个都关联于对应的文档中的 input 的 ID。通过循环这三项定义,依次为相应input添加了一个 onfocus 事件处理函数,以便显示帮助信息。

运行这段代码后,你会发现它没有达到想要的效果。无论焦点在哪个input上,显示的都是关于年龄的信息。

原因是赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量 item。这是因为变量item使用 var 进行声明,由于变量提升,所以具有函数作用域。当onfocus的回调执行时,item.help的值被决定。由于循环在事件触发之前早已执行完毕,变量对象item(被三个闭包所共享)已经指向了helpText的最后一项。

我们可以通过几种方法来解决这个问题。

解决方法一:使用更多的闭包

解决这个问题的一种方案是使用更多的闭包:特别是使用前面所述的函数工厂。

完整HTML

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title></title>
</head>
<body>
<p id="help">点击输入框时,这里显示提示信息</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 type="text/javascript">

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

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': '你的Email'},
      {'id': 'name', 'help': '你的名字'},
      {'id': 'age', 'help': '你的年龄'}
    ];

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

setupHelp();

</script>

</body>
</html>

demodownload

运行结果

 

这段代码可以如我们所期望的那样工作。所有的回调不再共享同一个环境, makeHelpCallback 函数为每一个回调创建一个新的词法环境。在这些环境中,help 指向 helpText 数组中对应的字符串。

解决方法二:使用匿名闭包

另一种方法是使用匿名闭包。

完整HTML

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title></title>
</head>
<body>
<p id="help">点击输入框时,这里显示提示信息</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 type="text/javascript">

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

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': '你的Email'},
      {'id': 'name', 'help': '你的名字'},
      {'id': 'age', 'help': '你的年龄'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    (function() {
       var item = helpText[i];
       document.getElementById(item.id).onfocus = function() {
         showHelp(item.help);
       }
    })(); // 马上把当前循环项的 item 与事件回调相关联起来
  }
}

setupHelp();

</script>

</body>
</html>

demodownload

执行结果

解决方法三:使用let关键词

如果不想使用过多的闭包,你可以用 ES2015 引入的 let 关键词,在for循环内把var改为let。参考文章:

完整HTML

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title></title>
 
</head>
<body>
<p id="help">点击输入框时,这里显示提示信息</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 type="text/javascript">

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

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': '你的Email'},
      {'id': 'name', 'help': '你的名字'},
      {'id': 'age', 'help': '你的年龄'}
    ];

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

setupHelp();

</script>

</body>
</html>

demodownload

执行结果同样是如预期的。这个例子使用let而不是var,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。

解决方法四:使用forEach遍历

另一个可选方案是使用 forEach()来遍历helpText数组并给每一个<p>添加一个监听器,如下所示:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title></title>
 
</head>
<body>
<p id="help">点击输入框时,这里显示提示信息</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 type="text/javascript">

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

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': '你的Email'},
      {'id': 'name', 'help': '你的名字'},
      {'id': 'age', 'help': '你的年龄'}
    ];

  helpText.forEach(function(text) {
    document.getElementById(text.id).onfocus = function() {
      showHelp(text.help);
    }
  });
}

setupHelp();

</script>

</body>
</html>

demodownload

总结

本文通过5个示例,介绍了在循环中创建闭包的常见错误,以及如何使用正确的方法。

相关文章

标签: 闭包  
x
x
  • 站长推荐