閉包
首先借用阮老師對閉包(closure)的概念做出的定義:
在《JavaScript高級程序設計(第3版)》中文版中[3]喇嘱,具體描述在第7章函數表達式第7.2節(jié)(頁碼為第178頁):閉包是指有權訪問另一個函數作用域中的變量的函數。創(chuàng)建閉包的常見方式塞栅,就是在一個函數內部創(chuàng)建另一個函數。
ECMAScript中腔丧,閉包指的是:
從理論角度:所有的函數放椰。因為它們都在創(chuàng)建的時候就將上層上下文的數據保存起來了。哪怕是簡單的全局變量也是如此愉粤,因為函數中訪問全局變量就相當于是在訪問自由變量砾医,這個時候使用最外層的作用域。
從實踐角度:以下函數才算是閉包:
- 即使創(chuàng)建它的上下文已經銷毀衣厘,它仍然存在(比如如蚜,內部函數從父函數中返回)
- 在代碼中引用了自由變量
閉包是怎么保存數據作為緩存數據使用?
無論什么時候在函數中訪問一個變量時影暴,就會從作用域鏈中搜索具有相應名字的變量错邦。一般來講,當函數執(zhí)行完畢后型宙,局部活動對象就會被銷毀撬呢,內存中僅保存全局作用域(全局執(zhí)行環(huán)境的變量對象)。但是閉包的情況又有所不同妆兑,閉包是一個函數捕獲它被定義時所在的環(huán)境魂拦,這個環(huán)境在該函數的引用被銷毀前都是存在的。
demo1:讀取上級作用域的活動變量
function foo() {
let x = 10;
// 閉包搁嗓,捕獲`foo`的環(huán)境芯勘。
// 當foo被調用時,創(chuàng)建foo的執(zhí)行環(huán)境腺逛,初始化變量對象(變量聲明和方法聲明以及參數)
//當捕捉到bar這個函數聲明時荷愕,會在函數內部創(chuàng)建bar的[[scope]]=foo的活動變量+foo的[[scope]]
function bar() {
return x;
}
return bar;
}
let x = 20;
// 調用`foo`來返回`bar`閉包。
//當執(zhí)行到這個地方的時候,相當于定義了一個函數bar路翻,bar的[[scope]]=foo的活動變量+foo的[[scope]]
// 只要全局作用域不銷毀狈癞,那么bar的[[scpoe]]就不會銷毀,因此形成閉包(調用的函數foo()執(zhí)行完畢后其執(zhí)行環(huán)境應該銷毀的茂契,但是由于此處的函數表達式而沒有銷毀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
);