第一次接觸這個問題仍是在我剛開始學js的時候,當時就是一頭霧水,時隔一年多了,忽然又想起了這個問題,在這個春氣盎然的週末,我就坐下來研究下並把結果和你們分享下;javascript
先看代碼:demo.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="gbk"/>
<title>閉包循環問題</title>
<style type="text/css">
p {background:red;}
</style>
</head>
<body>
<p id="p0">段落0</p>
<p id="p1">段落1</p>
<p id="p2">段落2</p>
<p id="p3">段落3</p>
<p id="p4">段落4</p>
<script type="text/javascript">
for( var i=0; i<5; i++ ) {
document.getElementById("p"+i).onclick=function() {
alert(i); //訪問了父函數的變量i, 閉包
};
};
</script>
</body>
</html>
每次循環就爲對應的編號段落上添加一個click事件,事件的回調函數是執行一個alert();若是你之前沒這麼用過的話,估計也會認爲單擊某個段落就會彈出這個段落相應的編號0,1,2,3,4。但其實是都是彈出5;css
網上已經有不少討論的博客了,他們給出了不少方法去實現彈出對應的編號。比較易於理解的方法以下:html
1,將變量i保存在對應的段落的某個屬性上:點擊查看效果。java
var pAry = document.getElementsByTagName("p"); for( var i=0; i< 5; i++ ) { pAry[i].no = i; pAry[i].onclick = function() { alert(this.no); } };
2,加一層閉包,i 以函數參數形式傳遞給內層函數:點擊查看效果。閉包
~function test() { var pAry = document.getElementsByTagName("p"); for( var i=0; i< pAry.length; i++ ) { (function(arg){ pAry[i].onclick = function() { alert(arg); }; })(i);//調用時參數 } }();
固然還有其餘一些方法,可是都不太好理解。
而我要探索的是,爲何demo.html中的返回值始終是5。網上的說法是「變量i是以指針或者變量地址方式保存在函數中」,由於只有按照這樣理解,才能解釋。但是僅僅憑藉一個結論怎麼才能服衆了?函數
談到指針或者變量地址這個話題,在C語言中卻是屢見不鮮了,可是在js這麼性感的語言中,除了對象的及其對象屬性的引用以外不多用到。一個基本的數據類型竟然和指針拉上關係了,這勾起了探索的慾望。this
3,試試下面的代碼 點擊查看效果 spa
~function test() { var temp =10; for( var i=0; i< 5; i++ ) { document.getElementById("p"+i).onclick=function() { alert(temp); //訪問了父函數的變量temp, 閉包 } }; temp=20; }();
它的執行結果是每一個段落的彈出都是20,而不是10。說明當咱們在單擊的那個時候,temp的值已是20。這是個彷佛不須要我來講明,很顯然的結果,由於在咱們單擊以前,temp已經被賦值爲20了。
4,再看看下面的代碼 點擊查看效果。
咱們在temp被改變值以前有程序去觸發單擊事件,彈出的是10;
指針
~function test() { var temp =10; for( var i=0; i< 5; i++ ) { document.getElementById("p"+i).onclick=function() { alert(temp); //訪問了父函數的變量i, 閉包 } if(i===1){ var evt = document.createEvent("MouseEvents"); evt.initEvent("click",true,true); document.getElementById("p1").dispatchEvent(evt); } }; temp=20; }();
這說明咱們在p1上綁定的單擊事件回調函數原本是要返回10的,當我再次手動去單擊p0段落時,彈出20的緣由是由於temp的值改變了。也就說明,每次彈出時,訪問到的是temp此刻的值,而不是綁定時候的值;這能夠說明變量temp確實是以變量地址保存的。擴展開去就是:函數內部訪問了與函數同級的變量,那麼該變量是常駐內存的。訪問該變量實質上是訪問的是變量的地址;htm
經過以上的結論,那麼咱們能夠簡單的描述閉包的本質了:在子做用域中保存了一份在父級做用域取得的變量,這些變量不會隨父級做用域的銷燬而銷燬,由於他們已常常駐內存了!
這句話也就說明了閉包的特性了:
1:由於常駐內存因此會形成內存泄露
2,只要其餘做用域能取到子做用域的訪問接口,那麼其餘做用域就有方法訪問該子做用域父級做用域的變量了。
看這樣一典型的閉包的例子: function A(){ var a=1; function B(){ return a; }; return B; }; var C=A();//C取得A的子做用域B的訪問接口 console.log(C());//1 C能訪問到B的父級做用域中的變量a 以上如有不足之處,歡迎指正,共同進步!