上篇簡單講述了閉包的特性之後,今天來點不同的舉例來加深印象!
先來看看以下程式碼:
1 | function createFunctionArray() { |
是不是會很好奇,為什麼輸出都是 5
?
回想一下閉包的特性,雖然上面的程式碼似乎應該依次輸出 0
、1
,
但實際上它們都輸出 5
的原因是因為在迴圈中創建的匿名函式捕獲了變數 i
的引用,而不是其值。
在 createFunctionArray
函式中,我們創建了一個函式陣列 functionArray
,
並使用一個迴圈來添加匿名函式到這個陣列中。這些匿名函式都捕獲了外部作用域的變數 i
。
當迴圈完成並退出後,i
的值等於 5
,因為這是使迴圈停止的條件。
由於這些匿名函式仍然引用相同的 i
,當呼叫這些函式時,都將使用當前的 i
值,即 5
。
這就是為什麼無論呼叫 functions[0]()
還是 functions[1]()
,都輸出 5
的原因。
那麼要如何讓上面的輸出結果是 0
、1
呢?
我們可以在每次迴圈迭代時創建一個新的作用域,這樣每個匿名函式都會捕獲不同的 i
值。
- 方式一:使用
let
來產生區塊作用域1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function createFunctionArray() {
var functionArray = [];
for (let i = 0; i < 5; i++) { // 使用 let 創建區塊作用域
functionArray.push(function() {
console.log(i);
});
}
return functionArray;
}
var functions = createFunctionArray();
functions[0](); // 輸出 0
functions[1](); // 輸出 1
使用 let
創建的 i
變數在每次迴圈迭代時都有自己的區塊作用域,
因此每個匿名函式都能正確地捕獲到其自己的 i
值。
- 方式二:IIFE (Immediately Invoked Function Expression)(立即呼叫函式表示法)
1 | function createFunctionArray() { |
這樣做之後,每個匿名函式都捕獲了它自己的 index
值,使得輸出正確。
IIFE 通常用於
- 創建私有作用域: IIFE 創建了一個獨立的作用域,其中的變數在函式執行後會被銷毀。
這有助於防止變數污染全域作用域。- 模組化程式碼: IIFE 可用於創建模組化的程式碼區塊,其中可以定義私有變數和函式,
並通過返回公共接口來封裝它們,以供外部使用。
閉包實際應用
封裝私有變數和函式:
閉包可以用來創建具有私有變數的函式,以增加安全性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function createCounter() {
let count = 0;
return {
increment: function () {
count++;
},
getCount: function () {
return count;
}
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 輸出 1事件處理程序:
閉包可用於處理事件。當使用者點擊按鈕時,可以使用閉包來記錄點擊次數。1
2
3
4
5
6
7const button = document.getElementById('likeButton');
let clickCount = 0;
button.addEventListener('click', function () {
clickCount++;
console.log(`點擊次數:${clickCount}`);
});setTimeout 和 setInterval:
使用閉包可以創建具有狀態的定時任務。1
2
3
4
5
6
7
8
9
10function createTimer() {
let seconds = 0;
return function () {
seconds++;
console.log(`過去秒數:${seconds}`);
};
}
const timer = createTimer();
setInterval(timer, 1000);模組模式:
閉包可用於創建模組,將相關的函式和數據封裝在一起,以提供更好的代碼組織。
假設我們正在建立一個計數器模組:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33const CounterModule = (function () {
// 私有變數
let count = 0;
// 增加
function increment() {
count++;
}
// 減少
function decrement() {
count--;
}
// 獲取當前值
function getCount() {
return count;
}
// 公開的部分
return {
increment: increment,
decrement: decrement,
getCount: getCount
};
})();
// 使用計數器模組
CounterModule.increment();
CounterModule.increment();
console.log(CounterModule.getCount()); // 輸出 2
CounterModule.decrement();
console.log(CounterModule.getCount()); // 輸出 1在這個例子中,
CounterModule
是一個使用閉包實現的模組,
包含了私有變數count
和三個公開方法:increment
、decrement
和getCount
。
這樣,我們可以使用模組來管理計數器的狀態,同時保護了count
變數,
使其無法被外部直接訪問或修改,提供了更好的代碼組織和隔離。快取:
閉包可用於創建簡單的快取,以避免重複計算。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function createCache() {
const cache = {};
return function (key) {
if (key in cache) {
return cache[key];
} else {
const result = /* 計算結果 */;
cache[key] = result;
return result;
}
};
}
const getValue = createCache();
console.log(getValue('data')); // 計算並快取結果
console.log(getValue('data')); // 直接使用快取的結果
深入了解不同的例子與閉包實際應用之後,相信大家有對閉包留下深刻的印象!
今天就先分享到這,我們下篇見!
參考資料:
➫ MDN - Closures
➫ [筆記]-JavaScript 閉包(Closure)是什麼?關於閉包的3件事
➫ [筆記] 談談JavaScript中closure的概念 – Part 2
➫ [JS] 深入淺出 JavaScript 閉包(closure)