說到閉包,我們首先來看一個最最簡單的例子,也是最最基礎的例子:爲多個相同的元素,綁定事件,在點擊每一個元素時,提示被點擊元素的排列位置。
<span style="font-size:14px;"> <div id = "test">
<p>欄目1</p>
<p>欄目2</p>
<p>欄目3</p>
<p>欄目4</p>
</div></span>
拿到手的第一反應就是for循環添加點擊事件了(添加索引值也可以!)
這裏討論閉包解決!(i=4 ,一直彈4,好煩!)
<span style="font-size:14px;">function bindClick(){
var allP = document.getElementById("test").getElementsByTagName("p"),
i=0,
len = allP.length;
for( ;i<len;i++){
allP[i].onclick = function(){ //匿名函數作爲回調函數
alert("you click the "+i+" P tag!");
//you click the 4 P tag!
}
}
}
bindClick();
//運行函數,綁定點擊事件</span>
這樣的
JS
處理,看起來沒有問題,可是在測試的時候,不管我們點擊哪一個p
標籤,我們獲取到的結果都是相同的,tell me why?說白了,這就是作用域到導致的一個問題。
下面來分析一下原因。首先呢,我們先把上述的JS
代碼給分解一下,讓我們看起來更容易理解。
<span style="font-size:14px;"> function bindClick(){
var allP = document.getElementById("test").getElementsByTagName("p"),
i=0,
len = allP.length;
for( ;i<len;i++){
allP[i].onclick = AlertP;
}
function AlertP(){ //非匿名函數作爲回調函數
alert("you click the "+i+" P tag!"); //發現i是未知的,沿着作用域查找i,但是i是經過for循環後得到的值,i=4
}
}
bindClick();
//運行函數,綁定點擊事件</span>
這裏應該沒有什麼問題吧,前面使用一個匿名函數作爲click
事件的回調函數,這裏使用的一個非匿名函數,作爲回調,完全相同的效果。也可以做下測試哦。
理解上面的說法了,那麼就可以很簡單的理解,爲什麼我們之前的代碼,會得到一個相同的結果了。首先看一下for
循環中,這裏我們只是對每一個匹配的元素添加了一個click
的回調函數,並且回調函數都是AlertP
函數。
這裏當爲每一個元素添加成功click
之後,i
的值,就變成了匹配元素的個數,也就是i=len
,而當我們觸發這個事件時,也就是當我們點擊相應的元素時,我們期待的是,提示出我們點擊的元素是排列在第幾個,這個時候,click
事件觸發,執行回調函數AlertP
但是當執行到這裏的時候,發現alert
方法中,有一個變量是未知的,並且在AlertP
的局部作用域中,也沒有查找到相應的變量,那麼按照作用域鏈的查找方式,就會向父級作用域去查找,這裏的父級作用域中,確實是有變量i
的,而i的值,卻是經過for
循環之後的值,i=len
。所以也就出現了我們最初看到的效果。
瞭解了這裏的原因,那麼解決方法也就很簡單了,控制這個作用域的問題唄,說白了,也就一個方法,那就是在回調函數中,
用一個局部變量,來記錄這個i
的值,這樣當再局部作用域中使用到i
變量時,就會使用優先使用局部變量中的i
變量的值。不會再去查找全局變量了。(定義索引值也是這個原理)
說到了這裏,大概也能理解一下閉包的概念了,按照之前我們說的作用域鏈的說法,當一個函數運行時,該函數就會被推入作用域鏈的前端,當函數執行結束,這個函數就會被推出作用域鏈,並且銷燬函數內部的局部變化和方法。
PS:閉包,說白了也就是在函數執行結束,作用域鏈將函數彈出之後,函數內部的一些變量或者方法,還可以通過其他的方法引用。
但是這裏呢,當bindClick
運行結束後,依然可以通過click
事件訪問到bindClick
函數內部的i變量,說明bindClick
函數內部的i
變量,在bindClick
結束後,並沒有被銷燬,這也就是閉包了。
重點來了,如何解決for循環中的閉包問題呢?
方法1:使得綁定click
事件的目標對象和變量i都變成局部變量。這裏可以直接把這兩者作爲形參,傳遞給另外的一個函數即可。(閉包中的傳參)
<span style="font-size:14px;"> function bindClick(){
var allP = document.getElementById("test").getElementsByTagName("p"),
i=0,
len = allP.length;
for( ;i<len;i++){
AlertP(allP[i],i);
}
function AlertP(obj,i){
obj.onclick = function(){
alert("you click the "+i+" P tag!");
}
}
}
bindClick();</span>
這裏,obj
和i
在AlertP
函數內部,就是局部變量了。click
事件的回調函數,雖然依舊沒有變量i
的值,但是其父作用域AlertP
的內部,卻是有的,所以能正常的顯示了,這裏AlertP
我放在了bindClick
的內部,只是因爲這樣可以減少必要的全局函數,放到全局也不影響的。
方法2.方法1添加了一個函數進行綁定,如果我不想添加函數呢!(
自執行函數)
<span style="font-size:14px;"> function bindClick(){
var allP = document.getElementById("test").getElementsByTagName("p"),
i=0,
len = allP.length;
for( ;i<len;i++){
allP[i].onclick = function (i){
return function(){
alert("you click the "+i+" P tag!");
}
}(i);
}
}
bindClick();</span>
閉包的應用
OK,這也是閉包的最簡單的應用了,其他的閉包寫法也有,只是就原理方面來說,和上面這種是相同的原理,所以這裏就不一一列舉了,用到閉包的地方其實很多(比如惰性載入函數,單例模式中的對象定義等),如果您能理解到這最簡單閉包的原理,那麼其他用到閉包的地方,見到了,也就能理解了。或者說,想要使用的時候,也就能想到應該怎麼用了吧。