之前對(duì)于閉包的理解只是很膚淺的,只是浮于表面,這次深究了一下閉包,下面是我對(duì)閉包的理解。
什么是閉包?
引用高程里的話 =>
閉包就是有權(quán)訪問另一個(gè)作用域中變量的函數(shù),閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組成而成
記住,閉包是一個(gè)函數(shù),廢話不多說,先來個(gè)例子:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
函數(shù)makeFunc里面定義了一個(gè)函數(shù),該函數(shù)引用了一個(gè)makeFunc內(nèi)部的變量,然后返回該函數(shù),myFunc變量承接了makeFunc返回的函數(shù),也就是
function displayName() { alert(name); }
在下面執(zhí)行該函數(shù),咦?。?!為什么函數(shù)里面的name是從哪里得到的,為什么不報(bào)錯(cuò)呢?
要搞明白其中的細(xì)節(jié),必須從理解函數(shù)被調(diào)用的時(shí)候都會(huì)發(fā)生什么入手
那么什么是作用域鏈呢?
當(dāng)一個(gè)函數(shù)被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境(執(zhí)行上下文),以及相應(yīng)的作用域,在函數(shù)執(zhí)行完的時(shí)候該作用域鏈會(huì)被銷毀,函數(shù)里面的變量也會(huì)被回收,但是閉包的情況不是這樣的..

還是上面的例子,打印了一下myFunc函數(shù),發(fā)現(xiàn)該函數(shù)的作用域鏈(scopes)保存著name變量,name變量并沒有被回收,于是我們成功的在makeFunc函數(shù)外的作用域取到了name變量,myFunc就是閉包,我們重溫一下閉包的特點(diǎn)=>閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組成而成.
這下概念就理解的更清楚了吧!
閉包與變量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<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 type="text/javascript">
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
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)' }
];
function returnHelp(i) {
return helpText[i];
}
function setupHelp() {
for (var i = 0; i < helpText.length; i++) {
var item = returnHelp(i);
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();
</script>
</body>
</html>
本來想實(shí)現(xiàn)一個(gè)點(diǎn)擊輸入框,顯示提示信息的功能,但是結(jié)果是顯示的全是age信息。。。為什么???
原因是賦值給onfouse的是閉包,這些閉包是由他們的函數(shù)定義和在setupHelp作用域中捕獲的環(huán)境所組成的 這三個(gè)閉包在循環(huán)中被創(chuàng)建,但他們共享同一個(gè)詞法作用域,在這個(gè)作用域中存在一個(gè)變量item, 點(diǎn)擊輸入框的時(shí)候,item已經(jīng)變成helpText 的最后一個(gè)了,所以。。。
怎么解決呢?
既然聚焦輸入框后的回調(diào)函數(shù)里面的item變量引用的是共享的,所以只要把他變成非共享的就可以了,
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
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)' }
];
function returnHelp(i) {
return helpText[i];
}
function setupHelp() {
for (var i = 0; i < helpText.length; i++) {
var item = returnHelp(i);
//通過這種方法把每一個(gè)item變成具體的實(shí)例,而不是變量,避免每一次聚焦的時(shí)候執(zhí)行函數(shù)里面的變量都是指向同一個(gè)數(shù)
document.getElementById(item.id).onfocus = change(item);
}
}
function change(item) {
return function() {
showHelp(item.help);
}
}
setupHelp();
這樣把每一次循環(huán)的item傳給外面的函數(shù),然后外邊函數(shù)返回具體的實(shí)例而非變量,這樣就解決了問題。
下面再來看一個(gè)經(jīng)典的例子 :
<html>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<script>
var nodes = document.getElementsByTagName('div');
for(var i = 0, len = nodes.length; i < len; i++){
nodes[i].onclick = function(){
alert(i);
}
};
</script>
</body>
</html
如果你認(rèn)為點(diǎn)擊每一個(gè)div會(huì)返回對(duì)應(yīng)的索引,那么恭喜你,你掉坑了?。?!
沒錯(cuò),一開始我也掉坑了,可能大家會(huì)感到很疑惑,為什么點(diǎn)完全是5,因?yàn)辄c(diǎn)擊事件是異步的,點(diǎn)擊的時(shí)候變量 i 已經(jīng)變成5了,所以點(diǎn)擊的時(shí)候彈出的全是5,怎么解決呢,閉包來了
for(var i = 0, len = nodes.length; i < len; i++){
(function(i){
nodes[i].onclick = function(){
console.log(i);
}
})(i)
};
這段代碼是如何完美的解決問題的呢?
當(dāng)我們點(diǎn)擊的時(shí)候,回調(diào)函數(shù)里面的 i 引用的是外面閉包的 i ,這樣問題就被完美的解決了。
閉包的缺點(diǎn)
在上面分析的過程中也說到過,閉包不會(huì)被垃圾回收機(jī)制回收,會(huì)造成內(nèi)存泄漏,記得閉包使用完手動(dòng)把變量回收一下。