作用域的概念 同級作用域 在一個作用域中聲明相同名稱的變數會發生變數名衝突的問題。假如在作用域 A 中聲明一個變數 a,作用域 B 也聲明一個變數 a,兩個作用域的變數都互不影響。 // 作用域 A { let a = 0; console.log(a); } // 作用域 B { let a = ...
作用域的概念
同級作用域
在一個作用域中聲明相同名稱的變數會發生變數名衝突的問題。假如在作用域 A 中聲明一個變數 a,作用域 B 也聲明一個變數 a,兩個作用域的變數都互不影響。
// 作用域 A
{
let a = 0;
console.log(a);
}
// 作用域 B
{
let a = 10;
console.log(a);
}
第一個列印 0,第二個列印 10。
嵌套作用域
作用域是可以嵌套的,作用域 A 嵌套作用域 B,此時兩個作用域分別聲明變數 a,也是不衝突的。
{// 作用域 A
let a = 0;
console.log(a);
{ // 作用域 B
let a = 10;
console.log(a);
}
}
第一個列印 0,第二個列印 10。
在嵌套作用域中,子作用域 B 可以訪問父作用域 A 的變數,也可以影響它的父作用域 A。
{
let a = 0;
{
a = 10;
console.log(a);
}
}
最終列印的結果是 10。
函數的閉包
JS 的類實際上就是在使用函數的閉包。每執行一次函數就是在生成一個新的作用域,這些新的作用域就像是上面提到的,它們互不影響,只有它們的子作用域可以影響父作用域,即閉包。
閉包緩存數據
function Counter(x) {
return {
add: y => {
return (x = x + y);
},
del: y => {
return (x = x - y);
}
};
}
add 和 del 都是父函數 Counter 的子函數,它們之間是一個閉包關係。創建兩個 Counter 的實例:
let c1 = Counter(10);
console.log(c1.add(20));
console.log(c1.del(40));
當執行c1.add()
時,左邊有一個 Closure,說明已經形成了一個閉包:
Closure 中只有 x 是受閉包影響被緩存下來的數據,也就是父函數 Counter 的變數。Local 代表 add 函數自身的變數,沒有與其他函數之間(或作用域)形成關係,也就不符合閉包的存在條件,只能由 add 函數自己來使用。
再繼續往下執行,可以看到此時 Closure 中的 x 已經是 30:
再往下執行,調用 del 函數,再執行函數的相減操作之間,我們可以看到 Closure 中的 x 還是上一次的結果:30。
再接著往下執行一次,del 函數相減之後,Closure 中的 x 的結果是 -10:
本節小結
受閉包的影響,父函數的數據被緩存下來,子函數可以自由地使用,而且再記憶體中也不會被銷毀。
閉包的好處
仔細觀察上面的例子,add 和 del 函數都依賴了相同的變數 x,而這個 x 是父函數給的,再閉包中被緩存起來。add 和 del 只需要傳遞新的參數就可以參與運算,也就是說,閉包可以減少我們函數的參數傳遞,使得我們一個計算操作更加連貫,且降低代碼耦合度。
假如不使用閉包,通過函數的參數傳遞來計算,替代上面的閉包函數:
function add(x, y) {
return (x = x + y);
}
function del(x, y) {
return (x = x - y);
}
let res = add(10, 20);
let ser = del(res, 40);
就很沒有必要,何不如把 x 抽離出來呢?變成一個全局變數:
let x = 10;
function add(y) {
return (x = x + y);
}
function del(y) {
return (x = x - y);
}
let res = add(20);
let ser = del(40);
可以,但是不推薦,全局作用域中,變數 x 被聲明一次,假如代碼越寫越多,變數是不是會衝突,代碼是不是變得難以維護?閉包可以把 add 和 del 以及 x 都囊括在一個作用域里,也不影響其他的作用域。
本節小節
閉包可以把一塊代碼容納在一個裡面,形成一個整體,一個不受其他作用域影響的作用域。是不是很像模塊開發?沒錯,我猜測 CommonJS 就是使用的閉包。
總結
-
閉包可以讓我們使用模塊開發思想來寫代碼,把一系列代碼揉進閉包里,是一個有機的結合。類就是使用的閉包,在早期通過閉包來實現模塊的開發。
-
閉包可以緩存父函數的變數,子函數可以使用,子函數修改父函數的變數,其他子函數也跟著改變。