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中閉包的原理,以及閉包的好處和弊端,在我們的代碼中合理的使用閉包,達到代碼的整潔和高效。
閱讀原文:javascript閉包詳解


浙公網安備 33010602011771號