之前傻傻分不清 匿名函數
和 閉包
這兩個概念,因此經常混用。閉包
是指有權訪問另一個函數作用域中的變量的函數。創建閉包的常見方式,就是在一個函數內部創建另一個函數。
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;
}
}
}
上例 return 了一個匿名函數,該函數內部使用了 propertyName
變量,而這個變量是外部函數中的成員,即使這個匿名函數被返回了,且在其他地方被調用了,它仍然可以訪問變量 propertyName
。
之所以還能訪問這個變量,是因為內部函數的作用域鏈中包含 createComparisonFunction()
的作用域。要徹底搞清楚其中的細節,必須從理解函數第一次被調用的時候都會發生什么入手。
當某個函數第一次被調用時,會創建一個執行環境及相應的作用域鏈,并把作用域鏈賦值給一個特殊的內部屬性(Scope)。然后,使用this、arguments和其他命名參數的值來初始化函數的活動對象(activation object)。但在作用域鏈中,外部函數的活動對象始終處于第二位,外部函數的外部函數的活動對象處于第三位,......直至作為作用域重點的全局執行環境。
在函數執行過程中,為讀取和寫入變量的值,就需要在作用域鏈中查找變量。
function compare(value1, value2) {
if (value < value2) {
return -1
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
上例代碼先定義了 compare() 函數,然后又在全局作用域中調用了它。當第一次調用 compare() 時,會創建一個包含 this 、 arguments 、 value1 和 value2 的活動對象。全局執行環境的變量對象(包含 this 、 result和 compare )在 compare() 執行環境的作用域鏈中則處于第二位。
看到這張圖我首先是悶逼的,包括閉包這一章,我已經反復度了多變,有一天靈關一閃,想說如果是我來實現這個閉包我會怎么玩??
先調用函數自身的作用域鏈,然后想尋找外圍的作用域,一直到頂。
要實現這個效果挺簡單的呀,弄一個數組,按順序把他們的變量存起來,在調用的時候遍歷一遍數組中的成員,看看有沒有我需要的變量不就成了嗎?
抱著這個想法再來看這張圖,中間的那個數組不就是我想要的東西嗎?因為這個函數只有一成,所以最外層的就是全局作用域了,數組0是函數本身,數組1是全局作用域。
函數指針指向的是這個數組,而這個指針同時又由全局作用域中的一個變量存儲,臥槽,全通了,那么如果在函數內部在加上一個變量,是不是數組的成員就多了一位,在數組的起始位置保存該函數的作用域,這個數組就達到了我想要的效果。
于是抱著想法繼續往下看...
var compare = createComparisonFunction("name");
var result = compare({ name: "Bert"}, {name: "Greg"});
從 createComparisonFunction() 中被返回后,它的作用域鏈被初始化為包含 createComparisonFunction() 函數的活動對象和全局變量對象。這樣,匿名函數就可以訪問在 createComparisonFunction() 中定義的所有變量。更為重要的是 createComparisonFunction() 函數在執行完畢后,其活動對象也不會被銷毀,因為匿名函數的作用域鏈仍然在引用這個活動對象。
當 createComparisonFunction() 函數返回后,其執行環境的作用域鏈會被銷毀,但它的活動對象仍然會留在內存中;知道匿名函數被銷毀后, createComparisonFunction() 的活動對象才會被銷毀。
var compareNames = createComparospmFimctopm("name");
// 調用函數
var result = compare({ name: "Bert"}, {name: "Greg"});
// 接觸對你們函數的引用(以便釋放內存)
compareNames = null;
看到這張圖,臥槽,就是這個樣,一個數組0是自建,1是父級,2是全局,如果父級多了就以此類推。
由于閉包會攜帶包含它的函數的作用域,因此會比其他函數占用更多的內存。過度使用閉包可能會導致內存占用過多,建議只在絕對必要時再考慮使用閉包。雖然像V8等優化后的JavaScript引擎會嘗試回收被閉包占用的內存,但還是要慎重使用閉包