在上一節中,詳細理解了做用域鏈和垃圾回收機制,彷佛這兩點跟閉包關係不大,可是仔細想想就會發現,其實否則。這一節將經過上一部分的說明詳細理解閉包。請看代碼:前端
function createComparisonFunction(propertyName) { return function(object1, object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2){ return -1; } else if (value1 > value2){ return 1; } else { return 0; } }; }
在這個例子中,突出的那兩行代碼是內部函數(一個匿名函數)中的代碼,這兩行代碼訪問了外部函數中的變量propertyName。即便這個內部函數被返回了,並且是在其餘地方被調用了,但它仍然能夠訪問變量propertyName。之因此還可以訪問這個變量,是由於內部函數的做用域鏈中包含createComparisonFunction()的做用域。數組
當某個函數被調用時,會建立一個執行環境(execution context)及相應的做用域鏈。而後,使用arguments 和其餘命名參數的值來初始化函數的活動對象(activation object)。但在做用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象處於第三位,……直至做爲做用域鏈終點的全局執行環境。閉包
在函數執行過程當中,爲讀取和寫入變量的值,就須要在做用域鏈中查找變量。函數
function compare(value1, value2){ if (value1 < value2){ return -1; } else if (value1 > value2){ return 1; } else { return 0; } } var result = compare(5, 10);
以上代碼先定義了compare()函數,而後又在全局做用域中調用了它。當調用compare()時,會建立一個包含arguments、value1 和value2 的活動對象。全局執行環境的變量對象(包含result和compare)在compare()執行環境的做用域鏈中則處於第二位。圖7-1 展現了包含上述關係的compare()函數執行時的做用域鏈。優化
後臺的每一個執行環境都有一個表示變量的對象——變量對象。全局環境的變量對象始終存在,而像compare()函數這樣的局部環境的變量對象,則只在函數執行的過程當中存在。在建立compare()函數時,會建立一個預先包含全局變量對象的做用域鏈,這個做用域鏈被保存在內部的[[Scope]]屬性中。當調用compare()函數時,會爲函數建立一個執行環境,而後經過複製函數的[[Scope]]屬性中的對象構建起執行環境的做用域鏈。此後,又有一個活動對象(在此做爲變量對象使用)被建立並被推入執行環境做用域鏈的前端。對於這個例子中compare()函數的執行環境而言,其做用域鏈中包含兩個變量對象:本地活動對象和全局變量對象。顯然,做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。spa
不管何時在函數中訪問一個變量時,就會從做用域鏈中搜索具備相應名字的變量。通常來說,當函數執行完畢後,局部活動對象就會被銷燬,內存中僅保存全局做用域(全局執行環境的變量對象)。可是,閉包的狀況又有所不一樣。指針
在另外一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到它的做用域鏈中。所以,在createComparisonFunction()函數內部定義的匿名函數的做用域鏈中,實際上將會包含外部函數createComparisonFunction()的活動對象。code
在匿名函數從createComparisonFunction()中被返回後,它的做用域鏈被初始化爲包含createComparisonFunction()函數的活動對象和全局變量對象。這樣,匿名函數就能夠訪問在createComparisonFunction()中定義的全部變量。更爲重要的是,createComparisonFunction()函數在執行完畢後,其活動對象也不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個活動對象。換句話說,當createComparisonFunction()函數返回後,其執行環境的做用域鏈會被銷燬,但它的活動對象仍然會留在內存中;直到匿名函數被銷燬後,createComparisonFunction()的活動對象纔會被銷燬。對象
因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多,咱們建議讀者只在絕對必要時再考慮使用閉包。雖然像V8 等優化後的JavaScript 引擎會嘗試回收被閉包占用的內存,但請你們仍是要慎重使用閉包。blog
繼續
function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ return i; }; } return result; }
這個函數會返回一個函數數組。表面上看,彷佛每一個函數都應該返本身的索引值,即位置0 的函數返回0,位置1 的函數返回1,以此類推。但實際上,每一個函數都返回10。由於每一個函數的做用域鏈中都保存着createFunctions() 函數的活動對象, 因此它們引用的都是同一個變量i 。當createFunctions()函數返回後,變量i 的值是10,此時每一個函數都引用着保存變量i 的同一個變量對象,因此在每一個函數內部i 的值都是10。
function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(num){ return function(){ return num; }; }(i); } return result; }
在重寫了前面的createFunctions()函數後,每一個函數就會返回各自不一樣的索引值了。在這個版本中,咱們沒有直接把閉包賦值給數組,而是定義了一個匿名函數,並將當即執行該匿名函數的結果賦給數組。這裏的匿名函數有一個參數num,也就是最終的函數要返回的值。在調用每一個匿名函數時,咱們傳入了變量i。因爲函數參數是按值傳遞的,因此就會將變量i 的當前值複製給參數num。而在這個匿名函數內部,又建立並返回了一個訪問num 的閉包。這樣一來,result 數組中的每一個函數都有本身num 變量的一個副本,所以就能夠返回各自不一樣的數值了。