|
|
|
|
|
在循環(huán)中創(chuàng)建閉包,很容易出現(xiàn)問題,本文將通過5個示例,介紹在循環(huán)中創(chuàng)建閉包的常見錯誤,以及如何使用正確的方法。
在循環(huán)中創(chuàng)建閉包的常見錯誤
在循環(huán)中有一個常見的閉包創(chuàng)建問題,我們先來看看例子。
完整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>
示例的預(yù)期是,當(dāng)點擊輸入框時,顯示對應(yīng)的提示信息。
不過執(zhí)行結(jié)果卻不符合預(yù)期,點擊輸入框時,提示信息并不跟著變化。無論焦點在哪個input
上,顯示的都是關(guān)于年齡的信息。
出現(xiàn)這個原因是在循環(huán)中使用的閉包有問題。
數(shù)組 helpText
中定義了三個有用的提示信息,每一個都關(guān)聯(lián)于對應(yīng)的文檔中的 input
的 ID。通過循環(huán)這三項定義,依次為相應(yīng)input
添加了一個 onfocus
事件處理函數(shù),以便顯示幫助信息。
運(yùn)行這段代碼后,你會發(fā)現(xiàn)它沒有達(dá)到想要的效果。無論焦點在哪個input
上,顯示的都是關(guān)于年齡的信息。
原因是賦值給 onfocus
的是閉包。這些閉包是由他們的函數(shù)定義和在 setupHelp
作用域中捕獲的環(huán)境所組成的。這三個閉包在循環(huán)中被創(chuàng)建,但他們共享了同一個詞法作用域,在這個作用域中存在一個變量 item
。這是因為變量item
使用 var
進(jìn)行聲明,由于變量提升,所以具有函數(shù)作用域。當(dāng)onfocus
的回調(diào)執(zhí)行時,item.help
的值被決定。由于循環(huán)在事件觸發(fā)之前早已執(zhí)行完畢,變量對象item
(被三個閉包所共享)已經(jīng)指向了helpText
的最后一項。
我們可以通過幾種方法來解決這個問題。
解決方法一:使用更多的閉包
解決這個問題的一種方案是使用更多的閉包:特別是使用前面所述的函數(shù)工廠。
完整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>
運(yùn)行結(jié)果
這段代碼可以如我們所期望的那樣工作。所有的回調(diào)不再共享同一個環(huán)境, makeHelpCallback
函數(shù)為每一個回調(diào)創(chuàng)建一個新的詞法環(huán)境。在這些環(huán)境中,help
指向 helpText
數(shù)組中對應(yīng)的字符串。
解決方法二:使用匿名閉包
另一種方法是使用匿名閉包。
完整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);
}
})(); // 馬上把當(dāng)前循環(huán)項的 item 與事件回調(diào)相關(guān)聯(lián)起來
}
}
setupHelp();
</script>
</body>
</html>
執(zhí)行結(jié)果
解決方法三:使用let關(guān)鍵詞
如果不想使用過多的閉包,你可以用 ES2015 引入的 let
關(guān)鍵詞,在for
循環(huán)內(nèi)把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>
執(zhí)行結(jié)果同樣是如預(yù)期的。這個例子使用let
而不是var
,因此每個閉包都綁定了塊作用域的變量,這意味著不再需要額外的閉包。
解決方法四:使用forEach遍歷
另一個可選方案是使用 forEach()
來遍歷helpText
數(shù)組并給每一個<p>
添加一個監(jiān)聽器,如下所示:
<!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>
總結(jié)
本文通過5個示例,介紹了在循環(huán)中創(chuàng)建閉包的常見錯誤,以及如何使用正確的方法。
相關(guān)文章