javascript閉包詳解
今天我們從記憶體結構上來講解下 javascript中的閉包概念。
閉包:是指有權訪問另外一個函數範圍中的變數的函數。建立閉包的常見方式就是在一個函數內部建立另外一個函數。
在javascript中沒有塊級範圍,一般為了給某個函數申明一些只有該函數才能使用的局部變數時,我們就會用到閉包,這樣我們可以很大程度上減少全域範圍中的變數,淨化全域範圍。
使用閉包有如上的好處,當然這樣的好處是需要付出代價的,代價就是記憶體的佔用。
如何理解上面的那句話呢?
每個函數的執行,都會建立一個與該函數相關的函數執行環境,或者說是函數執行內容。這個執行內容中有一個屬性 scope chain(範圍鏈指標),這個指標指向一個範圍鏈結構,範圍鏈中的指標又都指向各個範圍對應的使用中的物件。正常情況,一個函數在調用開始執行時建立這個函數執行內容及相應的範圍鏈,在函數執行結束後釋放函數執行內容及相應範圍鏈所佔的空間。
比如:
//聲明函數 function test(){ var str = "hello world"; console.log(str); } //調用函數 test();
在調用函數的時候會在記憶體中產生如的結構:
但是閉包的情況就有點特殊了,由於閉包函數可以訪問外層函數中的變數,所以外層函數在執行結束後,其範圍使用中的物件並不會被釋放(注意,外層函數執 行結束後執行環境和對應的範圍鏈就會被銷毀),而是被閉包函數的範圍鏈所引用,直到閉包函數被銷毀後,外層函數的範圍使用中的物件才會被銷毀。這也正是 閉包要佔用記憶體的原因。
所以使用閉包有好處,也有壞處,濫用閉包會造成記憶體的大量消耗。
使用閉包還有其他的副作用,可以說是bug,也可以說不是,相對不同的業務可能就會有不同的看法。
這個副作用是閉包函數只能取到外層函數變數的最終值。
測試代碼如下:(這裡使用了jquery對象)
/*閉包缺陷*/ (function($){ var result = new Array(), i = 0; for(;i<10;i++){ result[i] = function(){ return i; }; } $.RES1 = result; })(jQuery); // 執行數組中的函數 $.RES1[0]();
上面的代碼先通過匿名函數運算式開闢了一塊私人範圍,這個匿名函數就是我們上面所說的外層函數,該外層函數有一個參數$,同時還定義了變數result和 I , 通過for迴圈給數組result賦值一個匿名函數,這個匿名函數就是閉包,他訪問了外層函數的變數I , 理論上數組result[i]() 會返回相應的數組下標值,實際情況卻不如所願。
如上代碼 $.RES1[0]() 的執行結果是10.
為什麼會這樣呢,因為i的最終值就是10.
下面我們通過來詳細說明下,上面的那段代碼執行時在記憶體中到底發生了什麼:
那麼這個副作用有沒有辦法可以修複呢?當然可以!
我們可以通過下面的代碼來達到我們的預期。
/*修複閉包缺陷*/ (function($){ var result = new Array(), i = 0; for(;i<10;i++){ result[i] = function(num){ return function(){ return num; } }(i); } $.RES2 = result; })(jQuery); //調用閉包函數 console.log($.RES2[0]());
上面的代碼又在記憶體中發生了什嗎?我們同樣用下面的一幅圖來詳細解釋。看懂了上面的圖,我們也就不難理解下面的圖。
閱讀原文:javascript閉包詳解