JS學習筆記#21 | 閉包(Closure)

JS學習筆記#21 | 閉包(Closure)

更新於 發佈於 閱讀時間約 5 分鐘

一、什麼是閉包?

閉包是指一個函數能夠「記住」它被創建時的外部環境(作用域),即使那個外部環境已經不存在了。

簡單來說,閉包就像是函數帶著一個「記憶背包」,裡面裝著它出生時能看到的變數。


二、閉包怎麼形成的?

讓我們從一個簡單的範例開始:

function outer() {
let name = "小明";
function inner() {
console.log(name); // 存取外層的 name
}
return inner; // 把內層函數返回
}

const myFunc = outer(); // outer 執行完,得到 inner 函數
myFunc(); // 輸出:小明

步驟解析

  1. outer() 執行時,定義了 name = "小明" 和 inner 函數。
  2. inner 被返回並存到 myFunc 中。
  3. outer 執行完後,按理說 name 應該消失,但 inner 卻保留了對 name 的存取權。
  4. 呼叫 myFunc() 時,仍然能輸出 "小明"。

閉包形成的條件

  1. 有一個內層函數(nested function)。
  2. 這個內層函數存取了外層函數的變數(比如 inner)。
  3. 內層函數被「帶到外面」使用(比如返回出去,或被外部變數保存)。


三、閉包的運作原理

閉包的背後是 作用域鏈(Scope Chain)

  • 當 inner 被創建時,它記住了自己能看到的變數(name)。
  • 這個記憶不是複製一份 name 的值,而是保留對 name 的「參考」(reference)。
  • 所以即使外層函數結束,內層函數還是能透過參考找到這些變數。


四、閉包的實際應用

1.計數器(狀態管理)

function createCounter() {
let count = 0; // 外層變數
return function() {
count++; // 內層函數改變它
console.log(count);
};
}

const counter = createCounter();
counter(); // 輸出:1
counter(); // 輸出:2
counter(); // 輸出:3
  • count 是 createCounter 裡的變數,外界無法直接存取,只能透過返回的函數操作。
  • 返回的函數形成閉包,記住並操作 count,就像一個帶記憶的計數器。


2.製作按鈕範例

function createButton(text) {
let message = `你點了 ${text} 按鈕`;
return function() {
console.log(message);
};
}

const btn1 = createButton("確認");
const btn2 = createButton("取消");

btn1(); // 輸出:你點了 確認 按鈕
btn2(); // 輸出:你點了 取消 按鈕

每個按鈕函數都記住了自己獨特的 message,因為閉包讓它們各自帶著自己的「記憶背包」。


3. 修復迴圈問題

// 問題:var 是函數作用域,i 被共享,迴圈結束時 i = 3。
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 輸出:3, 3, 3
}, 1000);
}

// 閉包解法:
//每個 IIFE 創建了一個獨立的作用域,num 被「封裝」在閉包中,獨立於外層的 i。
//閉包讓每個 setTimeout 回調記住自己專屬的 num 值。
for (var i = 0; i < 3; i++) {
(function(num) {
setTimeout(function() {
console.log(num); // 輸出:0, 1, 2
}, 1000);
})(i);
}


五、閉包的優點

  1. 資料隱私:像計數器一樣,外界無法直接改動 count,只能透過閉包提供的函數操作。
  2. 狀態記憶:閉包能保存變數狀態,像按鈕範例記住每個按鈕的訊息。
  3. 靈活性:可以用來解決非同步或迴圈中的變數共享問題。


六、閉包的注意事項

記憶體問題:閉包記住的變數不會被回收,如果不小心保留大物件,可能佔用記憶體。

function heavyClosure() {
let bigData = new Array(1000000).fill("資料");
return function() {
console.log(bigData[0]);
};
}
const func = heavyClosure(); // bigData 一直存在

解法:用完閉包後,讓它可以被回收(例如設為 null)。




avatar-img
koko的沙龍
1會員
34內容數
留言
avatar-img
留言分享你的想法!
koko的沙龍 的其他內容
React 事件處理:讓網頁動起來~ 網頁的互動性是吸引使用者、提供良好體驗的關鍵。 在 React 中,透過監聽使用者的操作(例如點擊、滑鼠移動、鍵盤輸入),並執行相應的程式碼,來實現豐富的互動效果。
在 React 的世界裡,Props 負責從父元件向子元件傳遞資料,而 State 則是負責管理元件自身的內部資料。 State 就像元件的記憶,可以儲存元件的狀態,並根據狀態的變化來更新 UI。
在 React 的世界裡,元件 (Component) 就像一個個獨立的個體,各自負責 UI 的一部分,要讓這些個體協同工作,就需要一種溝通的機制,而 Props 就是組件之間通信和數據傳遞的主要方式。
React 事件處理:讓網頁動起來~ 網頁的互動性是吸引使用者、提供良好體驗的關鍵。 在 React 中,透過監聽使用者的操作(例如點擊、滑鼠移動、鍵盤輸入),並執行相應的程式碼,來實現豐富的互動效果。
在 React 的世界裡,Props 負責從父元件向子元件傳遞資料,而 State 則是負責管理元件自身的內部資料。 State 就像元件的記憶,可以儲存元件的狀態,並根據狀態的變化來更新 UI。
在 React 的世界裡,元件 (Component) 就像一個個獨立的個體,各自負責 UI 的一部分,要讓這些個體協同工作,就需要一種溝通的機制,而 Props 就是組件之間通信和數據傳遞的主要方式。