1.概念
閉包是指有權訪問另一個函數(shù)作用域中的變量的函數(shù)钉凌。創(chuàng)建閉包的常見方式建钥,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)选调。
2.示例說明
有關如何創(chuàng)建作用域鏈以及作用域鏈有什么作用的細節(jié)骤公,對徹底理解閉包至關重要堕义。
// 代碼示例 1
function createComparisonFunction(propertyName) {
return function(object1, object2) {
let value1 = object1[propertyName];
let value2 = object2[propertyName];
if(value1 < value2) {
return -1;
} else if(value1 > value2) {
return 1;
} else {
return 0;
}
}
}
// 創(chuàng)建函數(shù)
let compareNames = createComparisonFunction('name');
// undefined
// 調(diào)用函數(shù)
let result = compareNames({name: 'Zhang'}, {name: 'Li'});
// 調(diào)用函數(shù)
let out = compareNames({name: 'Zhang'}, {name: 'Li'});
// undefined
out;
// 1
分析上述示例 1 代碼:
在匿名函數(shù)從 createComparisonFunction()
函數(shù)中被返回后坏瞄,它的作用域鏈被初始化為包含 createComparisonFunction()
函數(shù)的活動對象和全局變量對象桂对。createComparisonFunction()
函數(shù)在執(zhí)行完畢后,其活動對象也不會被銷毀鸠匀,因為匿名函數(shù)的作用域鏈仍然在引用這個活動對象蕉斜。換句話說,當 createComparisonFunction()
函數(shù)返回后缀棍,其執(zhí)行環(huán)境的作用域鏈會被銷毀宅此,但它的活動對象仍然會留在內(nèi)存中,直到匿名函數(shù)被銷毀后爬范,createComparisonFunction()
的活動對象才會被銷毀父腕。
由于閉包會攜帶包含其它的函數(shù)的作用域,因此會比其它函數(shù)占用更多的內(nèi)存青瀑。 慎重使用閉包璧亮。
3.閉包與變量
作用域鏈的這種配置機制引出了一個值得注意的副作用,即閉包只能取得包含函數(shù)中任何變量的最后一個值斥难。閉包所保存的是整個變量對象枝嘶,而不是某個特殊的變量。
// 代碼示例 2
function createFunction() {
let result = new Array();
for(var i = 0; i < 10; i++) {
result[i] = function() {
return i;
}
}
return result;
}
上述代碼示例 2 中哑诊,每個匿名函數(shù)的作用域鏈中都保存著 createFunction()
函數(shù)的活動對象群扶,所以它們引用的都是同一個 i。當 createFunction()
函數(shù)返回后镀裤,變量 i 的值是 10穷当,而每個函數(shù)都引用著保存變量 i 的同一個變量對象,每個函數(shù)內(nèi)部 i 的值都是 10淹禾。
// 代碼示例 3
function createFunction() {
let result = new Array();
for(let i = 0; i < 10; i++) {
result[i] = function(num) {
return function() {
return num;
}
}(i);
}
return result;
}
代碼示例 3 中,通過創(chuàng)建另一個匿名函數(shù)強制讓閉包的行為符合預期茴扁。在調(diào)用每個匿名函數(shù)時铃岔,我們傳入了變量 i,由于函數(shù)參數(shù)是按值傳遞的,所以就會將變量 i 的當前值復制給參數(shù) num毁习。而這個匿名函數(shù)內(nèi)部智嚷,又創(chuàng)建并返回了 num 的閉包。這樣一來纺且, result 數(shù)組中的每個函數(shù)都有自己 num 變量的一個副本盏道,因此可以返回各自不同的副本。
總結(jié)
閉包载碌,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)猜嘱。里面函數(shù)的作用域鏈中引用了外部函數(shù)的活動對象,外部函數(shù)調(diào)用執(zhí)行完成時嫁艇,外部函數(shù)的作用域鏈會銷毀朗伶,而因為里面函數(shù)的作用域鏈中引用了外部函數(shù)的活動對象,只有在里面函數(shù)的執(zhí)行環(huán)境銷毀后步咪,外部函數(shù)的活動對象才你銷毀论皆。使用過多閉包會使內(nèi)存占用過多。
對于閉包的情況猾漫,閉包函數(shù)只有獲取包含函數(shù)中任何變量的最后一個值点晴。我們可以通過在匿名函數(shù)再次將閉包函數(shù)包裹起來,而閉包參數(shù)的引用通過匿名函數(shù)引用包含函數(shù)的值傳遞給參數(shù)悯周。
注:文章參考總結(jié)自 《JavaScript 高級程序設計》(第 3 版)[美] Nicholas C.Zakas 著 第 178 頁粒督。