[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>
示例的预期是,当点击输入框时,显示对应的提示信息。
不过执行结果却不符合预期,点击输入框时,提示信息并不跟着变化。无论焦点在哪个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>
运行结果
这段代码可以如我们所期望的那样工作。所有的回调不再共享同一个环境, 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>
执行结果
解决方法三:使用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>
执行结果同样是如预期的。这个例子使用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>
总结
本文通过5个示例,介绍了在循环中创建闭包的常见错误,以及如何使用正确的方法。
相关文章
标签: 闭包
- 站长推荐