歡迎移步個人博客閱讀:《理解閉包》javascript
閉包 是指能夠包含自由(未綁定到特定對象)變量的代碼塊;這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。「閉包」 一詞來源於如下二者的結合:要執行的代碼塊(因爲自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和爲自由變量提供綁定的計算環境(做用域)。html
閉包的一個重點在於做用域,在 JavaScript 中變量的做用域分兩種:全局變量與局部變量,首先讓咱們來了解一下:java
var _global = 1; // 全局變量 function print() { var _internal = 2; // 局部變量 console.log(_global); // 1 console.log(_internal); // 2 return _internal; } print(); console.log(_global); // 1 console.log(_internal); // ReferenceError: _internal is not defined
此時咱們能夠看到,在函數內部是能夠直接讀取全局變量的。但當咱們在外部想訪問內部變量時,就會報錯,由於在函數體外部時沒法訪問函數內部的變量的。es6
須要注意的是,當在函數內部定義變量時沒用使用 var
等聲明變量,那麼它實際上會成爲一個全局變量:閉包
function print() { _internal = 2; } console.log(_internal); // 2
從內存中解釋,變量的聲明都存在棧中,而在 JavaScript 中存在垃圾回收機制(garbage collection),當一個函數執行完返回以後,它的內存會被自動回收,此時函數內部的變量都會被銷燬。函數
那麼咱們有什麼方法能夠保存這一內存,而且在外部訪問函數內部的變量呢 —— 閉包。oop
在正常狀況下,咱們在外部時沒法修改函數內部變量的值:性能
// 場景 1 function print(x) { var _internal = 1; console.log(_internal + 1); } print(1); // 2 // ... print(1); // 2
咱們能夠看到,不管 print()
調用多少次,打印的值都是 2
,_internal
的值都是 1
。學習
這是由於 JavaScript 中的垃圾回收機制,在屢次調用 print()
時,每一次都須要回收前一次的內存,以後再次申請新內存,所以 _internal
沒法在內存中繼續保存。url
換而言之,在每次調用 print()
時都須要爲其和內部的變量申請新的內存空間,第一次 _internal
的內存地址可能爲 0x...1
,在函數調用完成以後,這塊內存將被釋放,再次調用時 _internal
的內存地址可能就是 0x...2
了。所以它沒法再內存中被保存下來。
那麼咱們須要在外部使用函數內部的變量時,就須要在函數內部再聲明一個函數,並將其返回:
function print() { var _internal = 1; return function log() { console.log(_internal); } } var test = print(); test(); // 1
此時,咱們已經能夠從外部訪問 print()
函數內部的變量了。
當咱們須要對 print()
函數內部的 _internal
的值進行修改時,咱們能夠給它另一個函數:
// 場景 2 var add; function print() { var _internal = 1; add = function(x) { _internal += x; } return function log() { console.log(_internal); } } var test = print(); test(); // 1 add(1); test(); // 2
通過上述能夠看出,函數 print()
在通過 add()
運行以後,_internal
的值分別爲 1
和 2
,這就說明了 _internal
始終保存在內存中,並無在 var test = print();
調用時被回收。
這是由於 print()
內的 log()
做爲返回值,被賦給 test
這個全局變量,所以 log()
始終在內存中。而 log()
依賴 print()
而且能夠訪問 _internal
,因此 print()
也始終在內存中,並且在 var test = print();
調用時沒有被回收。
換而言之,當 _internal
在聲明的時候分配了內存,咱們能夠將其內存地址表示爲 0x...1
,在 print()
函數被調用以後應該會被回收,可是因爲上述緣由,沒有被回收,它的值將繼續保留在地址爲 0x...1
中。在外部可使用指針去尋址,並取得其值。
在循環體中,咱們可能遇到:
function loopA() { var arr = []; for(var i = 0; i < 10; i++) { arr[i] = function() { return i; } } return arr; } var test = loopA(); test[0](); // 10 test[1](); // 10 // ... test[9](); // 10
在上述例子中,咱們須要他們執行不一樣的參數獲得不一樣的值。可是一共建立了 10 次匿名函數,,他們都是共享同一個環境的。在匿名函數執行以前,循環早已完成,此時的匿名函數一局指向循環體中的最後一個值了。
解決方案 1:
在 es6
中咱們可使用 let
聲明:
function loopA() { var arr = []; for(let i = 0; i < 10; i++) { arr[i] = function() { return i; } } return arr; } var test = loopA(); test[0](); // 0 test[1](); // 1 // ... test[9](); // 9
解決方案 2:
將函數聲明放在循環體外部:
function loopA() { var arr = []; var func = function(n) { return n; } for(var i = 0; i < 10; i++) { arr[i] = func(i) } return arr; } var test = loopA(); test[0]; // 0 test[1]; // 1 test[9]; // 9
解決方案 3:
function loopA() { var arr = []; for(var i = 0; i < 10; i++) { arr[i] = (function(i) { return i; })(i) } return arr; } var test = loopA(); test[0]; // 0 test[1]; // 1 test[9]; // 9
其餘解決方案請看參考
內存泄漏:因爲閉包會使得函數內部的變量都被保存在內存中,不會被銷燬,內存消耗很大。所以須要在退出函數以前,將不使用的變量都刪除。
會修改函數內部變量的值。
閉包是一種特殊的對象。它由兩部分構成:函數,以及建立該函數的環境。環境由閉包建立時在做用域中的任何局部變量組成。
若是不是由於某些特殊任務而須要閉包,在沒有必要的狀況下,在其它函數中建立函數是不明智的,由於閉包對腳本性能具備負面影響,包括處理速度和內存消耗。
百度百科 - 閉包
Wikipedia - Closure
學習 Javascript 閉包(Closure)
MDN - 閉包
深刻理解閉包系列第二篇——從執行環境角度看閉包
深刻理解閉包系列第四篇——常見的一個循環和閉包的錯誤詳解
深刻理解javascript原型和閉包(15)——閉包