閉包
何為閉包
閉包(Closure)是詞法閉包(Lexical Closure)的縮寫(xiě)
高級(jí)程序設(shè)計(jì)中寫(xiě)有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)
我覺(jué)得閉包不僅僅是一個(gè)函數(shù)
閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合。
因?yàn)殚]包在運(yùn)行時(shí)可以有多個(gè)實(shí)例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實(shí)例
理解閉包
函數(shù)創(chuàng)建過(guò)程
一個(gè)函數(shù)在第一次被調(diào)用時(shí)會(huì)創(chuàng)建對(duì)應(yīng)的函數(shù)上下文
?
1.確定 this 的值。
2.初始化詞法環(huán)境(LexicalEnvironment)。
-- 詞法環(huán)境由兩個(gè)部分組成
----環(huán)境記錄(enviroment record),存儲(chǔ)變量和函數(shù)聲明 [args,變量]
----對(duì)外部環(huán)境的引用(outer),可以通過(guò)它訪問(wèn)外部詞法環(huán)境 [[scope]]
3.變量環(huán)境(VariableEnvironment)?!揪褪且环N詞法環(huán)境】
?
js一次只能運(yùn)行一個(gè)上下文,每次進(jìn)去新的執(zhí)行上下文時(shí),會(huì)創(chuàng)建一個(gè)用于搜索變量和函數(shù)的作用域鏈,在執(zhí)行過(guò)程中讀取和寫(xiě)入值的時(shí)候都是在作用域中去查找變量
?
作用域鏈實(shí)際是一個(gè)變量對(duì)象的數(shù)組
聲明函數(shù)時(shí),先復(fù)制當(dāng)前環(huán)境的作用域 然后壓入當(dāng)前活動(dòng)對(duì)象【包括 this,args ,變量】
?
一般 函數(shù)的局部活動(dòng)對(duì)象在函數(shù)執(zhí)行完畢時(shí)就會(huì)銷(xiāo)毀,最后只保留全局作用域,而閉包不同
閉包外部的函數(shù)執(zhí)行完畢也不會(huì)被銷(xiāo)毀,因?yàn)殚]包的作用域鏈會(huì)引用這個(gè)函數(shù)的活動(dòng)對(duì)象
var e = 10;
function add(a){
var c = 10;
let d = 5;
return function(b){
//c++;
console.log('a'+a);
console.log('b'+b);
console.log("c"+c);
console.log("d"+d);
console.log("e"+e);
}
}
?
var add5 = add(5)
var add10 = add(10)
?
add5(5)
add10(5)</pre>
上個(gè)栗子中 add 在全局函數(shù)中定義,所以作用域就只有一個(gè)全局變量對(duì)象,
add5 是add 函數(shù)中返回的函數(shù)所以作用域中就有局部變量對(duì)象和全局變量對(duì)象,如下圖。



用處
閉包可以模擬私有方法
閉包就是攜帶狀態(tài)的函數(shù),并且它的狀態(tài)可以完全對(duì)外隱藏起來(lái)。 只能通過(guò)對(duì)外方法來(lái)修改,不能直接訪問(wèn)到變量
var counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
?
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1</pre>
函數(shù)工廠
可以生成函數(shù),例如中間件,進(jìn)去一個(gè)函數(shù),出來(lái)一個(gè)具有某種功能的函數(shù)
1.簡(jiǎn)化函數(shù)參數(shù),實(shí)現(xiàn)高階函數(shù)
比如說(shuō)之前的add 函數(shù),柯里化的實(shí)現(xiàn)(將復(fù)雜問(wèn)題分步求解,變得更簡(jiǎn)單化)
柯里化的特點(diǎn) 都是使用閉包的特性實(shí)現(xiàn)的
參數(shù)復(fù)用 – 復(fù)用最初函數(shù)的第一個(gè)參數(shù)
提前返回 – 返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)
延遲執(zhí)行 – 返回新函數(shù),等待執(zhí)行
2.閉包應(yīng)用場(chǎng)景之setTimeout 生成帶參數(shù)的定時(shí)器
//原生的setTimeout傳遞的第一個(gè)函數(shù)不能帶參數(shù)
setTimeout(function(param){
alert(param)
},1000)
?
//通過(guò)閉包可以實(shí)現(xiàn)傳參效果
function func(param){
return function(){
alert(param)
}
}
var f1 = func(1);
setTimeout(f1,1000);</pre>
3.回調(diào)函數(shù)
回調(diào)函數(shù)都是閉包
包括不限于 定時(shí)器 事件回調(diào)
閉包容易出現(xiàn)的問(wèn)題
- 閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值
function createFunctions(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(){
return i ;
}
}
return result;
}
var s =createFunctions();
s[1]() // 10
s[2]() // 10 和預(yù)期不符
`處于同一作用域`所以return 的變量 `i`是同一個(gè)引用 所以返回一致
?
解決方法是形成塊級(jí)作用域或者 把 `i` 保存下來(lái)
?
解決方法1
function createFunctions(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(num){
return function(){
return num ;
};
}(i);
}
return result;
}
解決方法2
function createFunctions(){
var result = new Array();
var j = 0;
for(let i=0; i<10; i++){
j++;
result[i] = function(){
return i+j ;
}
}
return result;
}
返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變量。
為什么使用let可以
因?yàn)閌let 可以產(chǎn)生塊級(jí)作用域` 如下圖中作用域中就多了一個(gè)塊級(jí)作用域保存了`i`的值

-
內(nèi)存回收問(wèn)題
閉包在內(nèi)部環(huán)境通過(guò)作用域鏈能訪問(wèn)到上層環(huán)境的變量,上層環(huán)境中的變量存在無(wú)法進(jìn)行變量回收的問(wèn)題,只要函數(shù)的作用域鏈在,變量的值便因?yàn)殚]包無(wú)法被回收??梢越獬谩?/p>
var s =createFunctions();
s = null ;// 解除引用 垃圾回收機(jī)制就會(huì)釋放內(nèi)存
?</pre>
總結(jié) 閉包
閉包是函數(shù)和詞法環(huán)境的組合
函數(shù)內(nèi)部可以訪問(wèn)外部的變量
使變量保持在內(nèi)存中
垃圾回收
js 具有自動(dòng)回收機(jī)制,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存
自動(dòng)回收機(jī)制
估計(jì)時(shí)間間隔回收 或代碼中預(yù)定回收時(shí)間
兩種策略回收無(wú)用變量
- 標(biāo)記清除 【常用】
進(jìn)入環(huán)境時(shí)標(biāo)記進(jìn)入 離開(kāi)環(huán)境時(shí)會(huì)被標(biāo)記清除,標(biāo)記清除的會(huì)被回收
- 引用計(jì)數(shù)
引用次數(shù)為0 時(shí)會(huì)被回收
管理內(nèi)存
解除引用
參考地址
深入理解JavaScript系列(16):閉包(Closures)
深入理解JavaScript系列(14):作用域鏈(Scope Chain)