閉包
首先借用阮老師對閉包(closure)的概念做出的定義:

closure.jpg
在《JavaScript高級程序設(shè)計(jì)(第3版)》中文版中[3],具體描述在第7章函數(shù)表達(dá)式第7.2節(jié)(頁碼為第178頁):閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包的常見方式,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)。
ECMAScript中,閉包指的是:
從理論角度:所有的函數(shù)。因?yàn)樗鼈兌荚趧?chuàng)建的時(shí)候就將上層上下文的數(shù)據(jù)保存起來了。哪怕是簡單的全局變量也是如此,因?yàn)楹瘮?shù)中訪問全局變量就相當(dāng)于是在訪問自由變量,這個(gè)時(shí)候使用最外層的作用域。
從實(shí)踐角度:以下函數(shù)才算是閉包:
- 即使創(chuàng)建它的上下文已經(jīng)銷毀,它仍然存在(比如,內(nèi)部函數(shù)從父函數(shù)中返回)
- 在代碼中引用了自由變量
閉包是怎么保存數(shù)據(jù)作為緩存數(shù)據(jù)使用?
無論什么時(shí)候在函數(shù)中訪問一個(gè)變量時(shí),就會從作用域鏈中搜索具有相應(yīng)名字的變量。一般來講,當(dāng)函數(shù)執(zhí)行完畢后,局部活動對象就會被銷毀,內(nèi)存中僅保存全局作用域(全局執(zhí)行環(huán)境的變量對象)。但是閉包的情況又有所不同,閉包是一個(gè)函數(shù)捕獲它被定義時(shí)所在的環(huán)境,這個(gè)環(huán)境在該函數(shù)的引用被銷毀前都是存在的。
demo1:讀取上級作用域的活動變量
function foo() {
let x = 10;
// 閉包,捕獲`foo`的環(huán)境。
// 當(dāng)foo被調(diào)用時(shí),創(chuàng)建foo的執(zhí)行環(huán)境,初始化變量對象(變量聲明和方法聲明以及參數(shù))
//當(dāng)捕捉到bar這個(gè)函數(shù)聲明時(shí),會在函數(shù)內(nèi)部創(chuàng)建bar的[[scope]]=foo的活動變量+foo的[[scope]]
function bar() {
return x;
}
return bar;
}
let x = 20;
// 調(diào)用`foo`來返回`bar`閉包。
//當(dāng)執(zhí)行到這個(gè)地方的時(shí)候,相當(dāng)于定義了一個(gè)函數(shù)bar,bar的[[scope]]=foo的活動變量+foo的[[scope]]
// 只要全局作用域不銷毀,那么bar的[[scpoe]]就不會銷毀,因此形成閉包(調(diào)用的函數(shù)foo()執(zhí)行完畢后其執(zhí)行環(huán)境應(yīng)該銷毀的,但是由于此處的函數(shù)表達(dá)式而沒有銷毀foo的執(zhí)行環(huán)境即bar的[[scope]],從而形成閉包)
let bar = foo();
bar(); // 10,而不是20!
demo2:讀寫上級作用域的活動變量
function createCounter() {
let count = 0;
return {
increment() { count++; return count; },
decrement() { count--; return count; },
};
}
let counter = createCounter();
console.log(
counter.increment(), // 1
counter.decrement(), // 0
counter.increment(), // 1
);