前言:最近在細讀Javascript高級程序設計,對於我而言,中文版,書中不少地方翻譯的差強人意,因此用本身所理解的,嘗試解讀下。若有紕漏或錯誤,會很是感謝您的指出。文中絕大部份內容引用自《JavaScript高級程序設計第三版》。前端
閉包是指有權訪問另外一個做用域中的變量的函數。閉包
建立閉包的常見方式, 就是在一個函數內部建立另一個函數。函數
function createComparisonFunction(propertyName) { return function(object1, object2) { var value1 = object1[propertyName]; // 注意, return出的匿名函數訪問了外部函數中的變量propertyname var value2 = object2[propertyName]; // 注意, return出的匿名函數訪問了外部函數中的變量propertyname if(value1 < value2) { return -1; } else if (value > value2) { return 1; } else { return 0; } } }
在這個例子中,突出的那兩行代碼是內部函數(一個匿名函數)中的代碼,這兩行代碼訪問了外部函數中的變量propertyName。優化
即便這個內部函數被返回了,並且是在其餘地方被調用了,但它仍然能夠訪問變量propertyName。翻譯
之因此還可以訪問這個變量,是由於內部函數的做用域鏈中包含createComparisonFunction()的做用域。設計
當某個函數被調用時,會建立有一個執行環境(execution context)以及相應的做用域鏈。
而後,使用arguments和其餘命名參數的值來初始化函數的活動對象(activation object)。指針
但在做用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象始終處於第三位,...直至做爲做用域鏈重點的全局執行環境。code
在函數執行過程當中,爲讀取和寫入變量的值,就須要在做用鏈中查找變量。對象
function compare(value1, value2) { if(value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } var result = compare(5,10);
以上代碼先定義了compare()函數,而後又在全局做用中調用了它。ip
當調用compare()時,會建立一個包含arguments、value1和value2的活動對象。
全局執行環境的變量對象(包含result和compare)在compare()執行環境的做用鏈中處於第二位。
function compare(value1, value2) { if(value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } var result = compare(5,10); //做用域鏈僞代碼 /* compare[[scope]] = { global variable: { result, compare }, local variable: { arguments: [5, 10], value1 : 5, value2: 10 } } */
後臺的每一個執行環境都有一個表示變量的對象——變量對象。
全局環境的變量對象始終存在,而compare()函數這樣的局部環境的變量對象,則只在函數執行的過程當中存在。
在建立compare()函數時,會建立一個預先包含全局變量對象的做用域鏈,這個做用域鏈被保存在內部的[[Scope]]屬性中。
當調用compare()函數時,會爲函數建立一個執行環境,而後經過複製函數的[[Scope]]屬性中的對象構建起執行環境的做用域鏈。此後,本地活動對象(也能夠說是函數執行時,內部的變量)被建立並被推入執行環境做用域的前端。
對於函數的執行環境而言,其做用域鏈中包含兩個變量對象:
1. 本地活動對象。
2. 全局變量對象。
做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不包含變量對象。
不管何時在函數中訪問一個變量時,就會從做用域鏈中搜索具備相應名字的變量。通常來說,當函數執行完畢後,局部活動對象就會被銷燬,內存中僅保存全局做用域(全局執行環境的變量對象)。
可是,閉包的狀況又有所不一樣。
在另外一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到它的做用鏈中。
所以, 在createComparisonFunction()函數內部定義的匿名函數的做用域鏈中,實際上將會包含外部函數createComparisonFunction()的活動對象。
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; } } } // 注意調用createComparisonFunction的返回值仍然是一個函數。 var compare = createComparisonFunction("name"); /* createComparisonFunction[[Scope]] = { global Variable: { createComparisionFunction }, local Variable: { arguments: ["name"], propertyName: "name" } } */ var result = compare({name: "Nicholas"}, {name:"Greg"}); /* compare[[Scope]] = { global Variable: { createComparisonFunction }, local Variable: { arguments: [{name: "Nicholas"}, {name:"Greg"}], value1: arguments[0][propertyName], value2: arguments[1][propertyName] } } //propertyName仍然引用createComparisonFunction中的propertyName */
createComparisonFuncion()函數在執行完畢後,其活動對象也不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個活動對象。
換句話說, 當createComparisonFunction()函數返回後, 其執行環境的做用域鏈會被銷燬,但它的活動對象仍然會留在內存中;直到匿名函數被銷燬後,createComparisonFunction()的活動對象想象纔會銷燬。
//建立函數 var compareNames = createComparisonFunction("name"); //調用函數 var result = compareNames({name: "Nicolas"}, { name: "Greg"}); //接觸對匿名函數的引用(以便釋放內存),固然若是你就是須要使用該變量,那麼就不用釋放了,也要看狀況的。 compareNames = null;
因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多,所以建議只在絕對必要時再考慮使用閉包。雖然像V8等優化後的JavaScript引擎會嘗試回收被閉包占用的內存,可是仍是要慎重的使用閉包。