閉包
TL;DR
- 閉包(closure)是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合。
- 要形成閉包須將函式宣告在另一個函式當中,內部函式能夠取得本身作用域以外的變數,就算外部函式已經被執行。
- 經常被應用在狀態保存、緩存機制、模擬私有變數以及柯里化。
- closure 主要用來封裝私有變數和方法;Class 主要用來建立一個建立物件的藍圖。
什麼是閉包 (closure)
根據 MDN 的定義,閉包(Closure)是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合。
簡單來說,要形成閉包須將函式宣告在另一個函式當中,內部函式能夠取得本身作用域以外的變數,就算外部函式已經被執行。
function outterFn() {
const name = 'jordan';
function innerFn() {
console.log(name);
}
return innerFn;
}
// 閉包 = innerFn + outterFn 的作用域環境
const innerFn = outterFn();
// 就算 outterFn 已經被執行,innerFn 仍能夠取得 name 參數
innerFn(); // jordan
實際應用
狀態保存
React 中的
useState
就是透過 closure 的概念實作,以下為一個簡易版的useState
,從下方程式碼可以發現,getState
與setState
可以透過 closure 的特性,讓內部函式記住外部的變數,因此可以透過呼叫setState
後,改變 state 的值,也可以透過getState
取得 state 最新的值。function useState(initialState) {
let state = initialState;
function getState() {
return state;
}
function setState(updatedState) {
state = updatedState;
}
return [getState, setState];
}
const [count, setCount] = useState(0);
count(); // 0
setCount(1);
count(); // 1緩存機制 (memoization)
緩存機制也是透過 closure 讓內部函式記住外部的變數的特性,將計算過的值儲存在 cache 物件中,當下次使用相同的引數 (argument)呼叫時,就可以不用重新計算,直接取得已經儲存在 cache 物件的值。
function memoize(cb) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (key in cache) return cache[key];
const result = cb.apply(this, args);
cache[key] = result;
return result;
};
}模擬私有變數
有時候我們不想讓暴露某些程式碼給外部使用,雖然 JavaScript 並不支援私有變數,但我們可以透過閉包達到類似的效果。
const counter = function () {
let privateCounter = 0;
return {
increment() {
privateCounter += 1;
},
decrement() {
privateCounter -= 1;
},
value() {
return privateCounter;
},
};
};
// privateCounter 無法被外部修改
// 因為 closure 的關係 increment 與 decrement 可以存取到 privateCounter
// 因此要修改 privateCounter 只能夠透過 increment 與 decrement
const counter1 = counter();
counter1.increment();
counter1.value(); // 1
counter1.privateCounter; // undefined柯里化
const add = function (x) {
return function (y) {
return x + y;
};
};
// 呼叫 add 函式並傳入引數 1
// 呼叫 increment 函式,increment 函式能夠取得之前傳入的引數 1
const increment = add(1);
increment(2); // 3
closure vs. Class
在 ES6 以前,JavaScript 還沒有 Class,我們如果要模擬 OOP 的行為,一般都是透過 closure 達成。
closure
function counter() {
let count = 0;
return {
increment: () => count++,
};
}
const counter1 = counter();
counter1.increment(); // 0
counter1.increment(); // 1
class
class Counter {
constructor() {
this.count = 0;
}
increment() {
return this.count++;
}
}
const counter2 = new Counter();
counter2.increment(); // 0
counter2.increment(); // 1
雖然 closure 和 Class 在封裝邏輯和私有變數有許多相似之處,但它們是不同的概念,有著不同的用途:
- closure:封裝私有變數和方法。
- Class:建立一個建立物件的藍圖。
closure 和 Class 還有一個很大的不同,Class 創造出來的實例每次呼叫都是同一個 Class 方法,而透過 closure 建立的實例每次都是回傳一個全新的物件,因此每次實例呼叫的都是不同的方法。
// 透過 closure 建立
const counter1 = count();
const counter2 = count();
console.log(counter1.increment === counter2.increment); // false
// 透過 class 建立
const counter3 = new Count();
const counter4 = new Count();
console.log(counter3.increment === counter4.increment); // true
參考來源:
https://medium.com/@sustained_salmon_fly_484/javascript 的物件導向-closure-new-class-809e0970d566
https://www.explainthis.io/en/interview-guides/javascript/what-is-closure
https://www.explainthis.io/zh-hant/interview-guides/javascript/what-is-closure