什麼是閉包? 簡單理解,當在一個函數的外部訪問函數內部定義的變數的時候就會形成一個閉包,由這個理解可以知道,當一個函數執行完成的時候,一般情況下,其作用域會被銷毀,其內部定義的變數也會變得不可訪問,所以閉包打破了這個現象。閉包造成一個函數執行完成之後,其創建的作用域不會被銷毀,因為它被函數外部的對象 ...
什麼是閉包?
簡單理解,當在一個函數的外部訪問函數內部定義的變數的時候就會形成一個閉包,由這個理解可以知道,當一個函數執行完成的時候,一般情況下,其作用域會被銷毀,其內部定義的變數也會變得不可訪問,所以閉包打破了這個現象。閉包造成一個函數執行完成之後,其創建的作用域不會被銷毀,因為它被函數外部的對象或者變數所引用。由此可知,閉包可以實現作用域的延時存在,但這也會造成記憶體的泄露。所以在明確知道自己需要使用閉包的時候,採取使用閉包,否則就不要使用閉包。
Eg:
function demo(){
var internal=10;
return function (){
alert(internal);
};
}
demo()();
以上的代碼執行之後會彈窗顯示10.我們在demo方法的外部列印了其內部的變數,這個就是閉包。
- 作用域產生的原理
我們知道,當執行一個函數的時候,其訪問變數的順序是,首先在其自己的作用域上查詢,如果查詢不到,則會往其上層作用域來查詢,一直查詢到最終根作用域(也就是window對象),如果還是查詢不到,則會拋出一個undefined的錯誤。
那麼作用域是如何產生的呢?
首先在執行js代碼之前,會產生一個全局作用域GO,也就是window對象(後面不再說明),而對於一個函數執行之前會創建自己的作用域AO,並持有其上層函數的作用域,以形成一個作用域鏈。
作用域創建的基本步驟:
1、 創建一個AO,並持有上層函數的作用域
2、 初始化形參和內部聲明的變數(變數聲明的提升),並初始化為undefined
3、 將形參和實參相統一
4、 函數定義整體提升
以一下代碼為例
function a(){
var a1=0;
function b(){
var b1=1;
function c(){
var c=2;
}
c();
}
b();
}
a();
當js執行之前會創建一個GO
GO{
a:function()…
}
執行a函數的時候會創建a函數的作用域AO
a_AO{
AO_chain:GO,
a1:0,
b:function()…
}
由以上定義可知
b的AO為
b_AO{
AO_Chain:a_AO,
b1:1,
c:function()…
}
c的作用域與此類似,由於此處重點是為了根據作用域來解釋閉包的原因,所以這裡不再詳細的說明作用域,如不瞭解請自行百度。
- 閉包
eg:有如下一個函數
function demo(){
var arr=[];
for(var i=0;i<10;i++){
arr[i]=function(){
document.write(i+ “ ”);
}
}
return arr;
}
var arr=demo();
for(var j=0;j<10;j++){
arr[j]();
}
熟悉閉包的人都知道最後列印的結果是10個10
10 10 10 10 10 10 10 10 10 10
大家知道這是由於閉包對變數的持有造成的,那麼根據作用域的原理是怎麼產生的呢?
- GO
GO{
arr = undefined,
demo = function()…
}
- demo 的AO
d_AO{
ao_chain:GO,
arr=[],//用於存放10個函數
i=0
}
- 當執行完成 arr=demo[]之後;
d_AO{
ao_chain:GO,
arr=[function()…,function()…{}],//10個函數,每個函數都是//function(){document.write(i + ‘ ’)}
i=10
}
- 執行arr數組中的函數的時候
a_AO{
ao_chain:d_AO
}
從上面的作用域鏈可以知道,當數組的函數想要訪問i變數的時候,發現它自己的作用域中沒有這個變數,所用會通過其作用域鏈進行查詢,然後在d_AO中找到了i對象,此時i對象的值變為了10,所以arr數組中的所有的函數最後列印的結果都是一樣的。
為瞭解決這個問題,可以使用立即執行函數
如下
function demo(){
var arr=[];
for(var i=0;i<10;i++){
(function(j){arr[j]=function(){
document.write(j+ “ ”);
}})(i);
}
return arr;
}
var arr=demo();
for(var j=0;j<10;j++){
arr[j]();
}
結果如圖
0 1 2 3 4 5 6 7 8 9
從作用域的解釋來看(如果對立即執行函數不瞭解的,請百度查閱立即執行函數的相關內容)
- GO
GO{
arr = undefined,
demo = function()…
}
- demo 的AO
d_AO{
ao_chain:GO,
arr=[],//用於存放10個函數
i=0
}
前兩步與上面的方法創建的作用域相同
第3步開始有些差異
- 執行demo函數的時候
在執行demo函數的時候,因為內部存在一個立即執行函數,所以這個匿名函數會被立即執行,它也會創建自己的作用域
n_AO{
ao_chain:d_AO,
j:n//這裡的n保存的是i的當前值,例如第一個n對應的為j:0
}
//對應的arr[j]=function(){document.write(j+ “ ”);},j不會被替換,仍然是保持引用
- 執行arr函數的時候
arr_AO{
ao_chain:n_AO//例如arr[1]對應的就是ao_chain:1_AO
}
從上面的作用域鏈可以看出,當執行arr中的函數的時候,例如執行arr[2]的時候,首先到自己的作用域查詢j變數,發現找不到j,於是沿著作用域鏈進行查找,首先查詢2_AO,在2_AO中找到了j變數,此時j變數的值為2,於是列印的結果就為2,這樣我們就實現了我們得需要。
以上解釋若有錯誤,還望指點,包涵。謝謝!