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
0會員
28內容數
留言
avatar-img
留言分享你的想法!
koko的沙龍 的其他內容
作用域是指程式中變數的可訪問範圍,也就是變數在哪裡可以被存取。 JavaScript 有幾種作用域類型: 1.全域作用域(Global Scope) 變數在程式最外層定義,任何地方都可以存取。 var globalVar1 = "我是全域的1"; let globalV
什麼是提升?在 JavaScript 中,提升是指變數和函數宣告會在程式執行前被「提升」到它們所在作用域(scope)的頂部。這是 JavaScript 引擎處理程式碼時的一種行為,讓你在宣告之前就能使用某些變數或函數。
什麼是執行環境(Execution Context)? 簡單來說,執行環境是 JavaScript 程式碼執行時所在的「環境」。 它決定了程式碼如何被解析和執行,並管理變數、函數以及作用域(scope)的存取。 每當程式碼執行時,JavaScript 引擎會建立一個執行環境。
for...of 需要迭代的是具有迭代器的可迭代物件。一般的物件,除非你為它定義迭代器,否則不能使用 for...of。它主要用來迭代「值」。 for...in 迭代的是物件的可枚舉屬性,在陣列中就會迭代索引。通常用來迭代物件屬性,在陣列中較不適合,也較容易出錯。
Spread Syntax和Rest Parameters都使用 ... 符號,Spread Syntax用於展開可迭代物件,例如將陣列或物件的元素複製到新的陣列或物件中,或是在函式呼叫時傳遞參數。Rest Parameters用於收集不定數量的函數參數,將其打包成一個陣列,方便在函數內部進行處理。
身為一名女性,我常常在生活中感受到性別帶來的無形壓力。 而閱讀上野千鶴子《厭女:日本的女性嫌惡》這本書後,讓我突然意識到原來身為女性,在這個社會上彷彿在玩著一款難度超高的遊戲。
5/5厭女:日本的女性嫌惡
作用域是指程式中變數的可訪問範圍,也就是變數在哪裡可以被存取。 JavaScript 有幾種作用域類型: 1.全域作用域(Global Scope) 變數在程式最外層定義,任何地方都可以存取。 var globalVar1 = "我是全域的1"; let globalV
什麼是提升?在 JavaScript 中,提升是指變數和函數宣告會在程式執行前被「提升」到它們所在作用域(scope)的頂部。這是 JavaScript 引擎處理程式碼時的一種行為,讓你在宣告之前就能使用某些變數或函數。
什麼是執行環境(Execution Context)? 簡單來說,執行環境是 JavaScript 程式碼執行時所在的「環境」。 它決定了程式碼如何被解析和執行,並管理變數、函數以及作用域(scope)的存取。 每當程式碼執行時,JavaScript 引擎會建立一個執行環境。
for...of 需要迭代的是具有迭代器的可迭代物件。一般的物件,除非你為它定義迭代器,否則不能使用 for...of。它主要用來迭代「值」。 for...in 迭代的是物件的可枚舉屬性,在陣列中就會迭代索引。通常用來迭代物件屬性,在陣列中較不適合,也較容易出錯。
Spread Syntax和Rest Parameters都使用 ... 符號,Spread Syntax用於展開可迭代物件,例如將陣列或物件的元素複製到新的陣列或物件中,或是在函式呼叫時傳遞參數。Rest Parameters用於收集不定數量的函數參數,將其打包成一個陣列,方便在函數內部進行處理。
身為一名女性,我常常在生活中感受到性別帶來的無形壓力。 而閱讀上野千鶴子《厭女:日本的女性嫌惡》這本書後,讓我突然意識到原來身為女性,在這個社會上彷彿在玩著一款難度超高的遊戲。
5/5厭女:日本的女性嫌惡
你可能也想看
Google News 追蹤
Thumbnail
全方位分析脫離繼承戰的方法,大膽猜測誰會成為卡丁國下一任國王。
Thumbnail
CSS 盒模型是理解和設計網頁佈局的核心概念。它包括元素的內容、填充、邊框和外邊距。
Thumbnail
JSDoc 全名是 JavaScript Documentation,顧名思義是為 JavaScript 所使用的 API 文件,在程式碼內透過註解的方式撰寫,運行後 JSDoc 會自動掃描註解內容,並生成一份網頁版的文件,對於沒有使用 Typescript 開發的專案,也
Thumbnail
※ 靜態資源回傳 ※ 什麼是靜態資源: 定義:是指事先準備好的資源,這些資源在伺服器上是靜態的、不會隨著每個請求而改變。 資源通常包括: 靜態 HTML 文件。 CSS。 圖像(Image)。 Video。 字體文件:google fonts。 favicon:網頁名稱旁邊的ico
Thumbnail
※ OPP第一大核心-封裝 封裝的精神在於將「方法」、「屬性」和「邏輯」包裝在類別裡面,透過類別的實例來實現。這樣外部物件不需要了解內部的實現細節,只需要知道如何使用該類別提供的接口即可。換句話說,封裝是將內部細節隱藏起來,只暴露必要的部分給使用者。 封裝的核心概念是,使用者如果想要接觸資料,只
※ 函式基礎介紹: ※ JavaScript 特殊的函式特性: 函式可以當成值來傳遞 (可以放進變數或放進物件) 函式可以當成函式的參數 callback - 在特定事件中觸發函式 (非同步特性) ※ 函式的基本寫法: ※ 調用 (invoke) 函式: "調用" 意指呼叫或執行
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
※ Git(四) 空的資料夾無法被加入 Git 進行版本控制,但這個資料夾如果又是很重要的資料夾,你會怎麼處理? 當空的目錄無法被加入 Git 進行版本控制時,有以下的做法可以解決: 在那個空目錄裡隨便放一個檔案就行了。 在這個資料夾中添加一個名為 .gitkeep 的空檔案,讓 Git 能
Thumbnail
在 2021 年的剛轉職成為前端工程師的時候,我在面試時滿常會被詢問到 JavaScript 中閉包的議題,當時候自己回答的滿差的,於是在 2022 年時,我寫了一系列的有關於函式程式設計鐵人賽的文章, 裡頭就有簡單提到有關於閉包的議題。
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
Thumbnail
全方位分析脫離繼承戰的方法,大膽猜測誰會成為卡丁國下一任國王。
Thumbnail
CSS 盒模型是理解和設計網頁佈局的核心概念。它包括元素的內容、填充、邊框和外邊距。
Thumbnail
JSDoc 全名是 JavaScript Documentation,顧名思義是為 JavaScript 所使用的 API 文件,在程式碼內透過註解的方式撰寫,運行後 JSDoc 會自動掃描註解內容,並生成一份網頁版的文件,對於沒有使用 Typescript 開發的專案,也
Thumbnail
※ 靜態資源回傳 ※ 什麼是靜態資源: 定義:是指事先準備好的資源,這些資源在伺服器上是靜態的、不會隨著每個請求而改變。 資源通常包括: 靜態 HTML 文件。 CSS。 圖像(Image)。 Video。 字體文件:google fonts。 favicon:網頁名稱旁邊的ico
Thumbnail
※ OPP第一大核心-封裝 封裝的精神在於將「方法」、「屬性」和「邏輯」包裝在類別裡面,透過類別的實例來實現。這樣外部物件不需要了解內部的實現細節,只需要知道如何使用該類別提供的接口即可。換句話說,封裝是將內部細節隱藏起來,只暴露必要的部分給使用者。 封裝的核心概念是,使用者如果想要接觸資料,只
※ 函式基礎介紹: ※ JavaScript 特殊的函式特性: 函式可以當成值來傳遞 (可以放進變數或放進物件) 函式可以當成函式的參數 callback - 在特定事件中觸發函式 (非同步特性) ※ 函式的基本寫法: ※ 調用 (invoke) 函式: "調用" 意指呼叫或執行
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
※ Git(四) 空的資料夾無法被加入 Git 進行版本控制,但這個資料夾如果又是很重要的資料夾,你會怎麼處理? 當空的目錄無法被加入 Git 進行版本控制時,有以下的做法可以解決: 在那個空目錄裡隨便放一個檔案就行了。 在這個資料夾中添加一個名為 .gitkeep 的空檔案,讓 Git 能
Thumbnail
在 2021 年的剛轉職成為前端工程師的時候,我在面試時滿常會被詢問到 JavaScript 中閉包的議題,當時候自己回答的滿差的,於是在 2022 年時,我寫了一系列的有關於函式程式設計鐵人賽的文章, 裡頭就有簡單提到有關於閉包的議題。
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相