理解閉包

閉包

何為閉包

閉包(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)題

  1. 閉包只能取得包含函數(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`的值
3
  1. 內(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é) 閉包

  1. 閉包是函數(shù)和詞法環(huán)境的組合

  2. 函數(shù)內(nèi)部可以訪問(wèn)外部的變量

  3. 使變量保持在內(nèi)存中

垃圾回收

js 具有自動(dòng)回收機(jī)制,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存

自動(dòng)回收機(jī)制

估計(jì)時(shí)間間隔回收 或代碼中預(yù)定回收時(shí)間

兩種策略回收無(wú)用變量

  1. 標(biāo)記清除 【常用】

進(jìn)入環(huán)境時(shí)標(biāo)記進(jìn)入 離開(kāi)環(huán)境時(shí)會(huì)被標(biāo)記清除,標(biāo)記清除的會(huì)被回收

  1. 引用計(jì)數(shù)

引用次數(shù)為0 時(shí)會(huì)被回收

管理內(nèi)存

解除引用

參考地址

深入理解JavaScript系列(16):閉包(Closures)

深入理解JavaScript系列(14):作用域鏈(Scope Chain)

閉包有話說(shuō)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容