閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合串慰。MDN
MDN上的栗子
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
- JavaScript中的函數(shù)會形成閉包。 閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組合而成。這個(gè)環(huán)境包含了這個(gè)閉包創(chuàng)建時(shí)所能訪問的所有局部變量菜职。
- myFunc 是執(zhí)行 makeFunc 時(shí)創(chuàng)建的 displayName 函數(shù)實(shí)例的引用,而 displayName 實(shí)例仍可訪問其詞法作用域中的變量旗闽,即可以訪問到 name 酬核。由此,當(dāng) myFunc 被調(diào)用時(shí)适室,name 仍可被訪問嫡意,其值 Mozilla 就被傳遞到alert中。
為什么要使用閉包捣辆?
局部變量無法共享和長久的保存蔬螟,而全局變量可能造成變量污染,所以我們希望有一種機(jī)制既可以長久的保存變量又不會造成全局污染汽畴。
怎么樣使用閉包旧巾?
- 定義外層的函數(shù)(outer),封裝被保護(hù)的局部變量忍些;
- 定義內(nèi)層的函數(shù)(inner)鲁猩,執(zhí)行對外層函數(shù)變量的引用;
- 外層函數(shù)(outer)返回內(nèi)層(inner)的函數(shù)對象罢坝,并且外層函數(shù)(outer)被調(diào)用廓握,結(jié)果保存在一個(gè)全局的變量中。
注意一:手動解除引用
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
// 釋放對閉包的引用
add5 = null;
add10 = null;
add5 和 add10 都是閉包嘁酿。它們共享相同的函數(shù)定義疾棵,但是保存了不同的環(huán)境。在 add5 的環(huán)境中痹仙,x 為 5是尔。而在 add10 中,x 則為 10开仰。最后通過 null 釋放了 add5 和 add10 對閉包的引用拟枚。
在javascript中薪铜,如果一個(gè)對象不再被引用,那么這個(gè)對象就會被垃圾回收機(jī)制回收恩溅;而makeAdder對象被全局變量add5和add10引用隔箍,就會占用內(nèi)存空間。
注意二:閉包遇上for循環(huán)
閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值脚乡,這是因?yàn)殚]包所保存的是整個(gè)變量對象蜒滩,而不是某個(gè)特殊的變量。
function test() {
var arr = [];
for(var i = 0;i < 10;i++) {
arr[i] = function() {
return i;
};
}
for(var a = 0;a < 10;a++) {
console.log(arr[a]());
}
}
test(); // 連續(xù)打印10個(gè)10
函數(shù)1作用域
for(var i = 0; i < 10; i++) { 函數(shù)1作用域
我在函數(shù)1作用域中
arr[i] = function() { 函數(shù)2作用域
我在函數(shù)2作用域中
return i;
};
}
函數(shù)1作用域
console.log(i);
console.log(i); 執(zhí)行到這里的時(shí)候奶稠,i 是10俯艰,既然這里是10,那么在函數(shù)2作用域中訪問i也是10锌订,因?yàn)楹瘮?shù)2作用域中沒有竹握,向上去函數(shù)1作用域
中找,同一作用域中同一變量名的變量值肯定是相同的(未修改的情況下)辆飘。
當(dāng)你用 let 的時(shí)候啦辐,如下:
塊1作用域
for(let i = 0; i < 10; i++) { 塊2作用域
我在塊2作用域中
console.log(i); // 毫無疑問,這里的i從0依次增加到10
arr[i] = function() { 塊3作用域
我在塊3作用域中
return i;
};
}
塊1作用域
當(dāng)你換成 let 的時(shí)候蜈项,讀取i的時(shí)候芹关,在當(dāng)前作用域塊3中沒有找到,向上一個(gè)作用域塊2尋找紧卒,在塊2作用域
中發(fā)現(xiàn)i侥衬,于是拿到值。
閉包的缺點(diǎn)
- 由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中常侦,內(nèi)存消耗很大浇冰,所以不能濫用閉包贬媒,否則會造成網(wǎng)頁的性能問題聋亡,在IE中可能導(dǎo)致內(nèi)存泄露。
- 閉包會在父函數(shù)外部际乘,改變父函數(shù)內(nèi)部變量的值坡倔。所以,如果你把父函數(shù)當(dāng)作對象(object)使用脖含,把閉包當(dāng)作它的公用方法(Public Method)罪塔,把內(nèi)部變量當(dāng)作它的私有屬性(private value)。
function Animal() {
// 私有變量
var series = "哺乳動物";
function run() {
console.log("Run!!!");
}
// 特權(quán)方法
this.getSeries = function() {
return series;
};
}
思考題
function fun(n,o){
console.log(o);
return {
fun: function(m){
return fun(m,n);
}
};
}
var a = fun(0); // ?
a.fun(1); // ?
a.fun(2); // ?
a.fun(3); // ?
var b = fun(0).fun(1).fun(2).fun(3); // ?
var c = fun(0).fun(1); // ?
c.fun(2); // ?
c.fun(3); // ?
參考文章
https://zhuanlan.zhihu.com/p/27857268
https://juejin.im/entry/57d60f7067f3560057e37e25
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html