Closure & Scope:
掌握 JS 的靈魂核心
為什麼函式執行完畢後,還能「記得」外部的變數?這不是魔法,而是 JavaScript 最強大也最令人困惑的機制:閉包與作用域。
Scope:你的變數住在哪裡?
Scope (作用域) 決定了變數的「可見性」。在 JavaScript 中,如果你在錯誤的地方呼叫變數,就會遇到經典的 ReferenceError。
Global
全域作用域
整個程式碼都看得到,像空氣一樣。
Function
函式作用域
只有函式內部才看得到,外部無法窺探。
Block
區塊作用域 (ES6+)
if/for 大括號內部的變數,由 let/const 守護。
程式碼範例:
一、Lexical Scope:決定身分的基因
JavaScript 採用的是 Lexical Scope (語彙作用域)。 簡單來說:一個變數的作用域是在「寫下程式碼的那一刻」就決定的,而不是在執行時決定。
為什麼這很重要?
因為它決定了函式在尋找變數時,會往哪一個「外層」去找。這個「尋找」的過程,我們稱為 Scope Chain (作用域鏈)。
* 因為 sayName 被定義在全域,它的外層就是全域,即便在 wrapper 內部呼叫,它依然只會找它「定義時」的外層。
二、Closure:打破生命週期的契約
當一個函式可以「記得」並「存取」語彙作用域,即使這個函式是在原始作用域之外執行,這就是 Closure (閉包)。
正常情況下,函式執行完畢後,記憶體會被回收。但閉包就像是一張「便利貼」,把函式執行時所需的環境變數給「黏」住了,不讓垃圾回收機制 (GC) 帶走它。
三、閉包實戰:建立私有變數
在現代 JS 推出 `#private` 語法之前,閉包是唯一實現「資料封裝」的方法。這在寫模組或套件時非常重要。
優點:安全性
保護內部狀態不被外部隨意篡改,只能透過你定義的 API (methods) 來更動資料。
缺點:記憶體消耗
只要閉包函式還在,內部的變數就不會被回收。如果建立太多沒在用的閉包,會影響效能。
四、經典陷阱:迴圈中的閉包
這是一個面試必考題:為什麼 setTimeout 裡的 `i` 總是印出最後一個數字?
❌ 錯誤的寫法 (使用 var)
* var 是函式作用域,整個迴圈共用同一個 i。當 setTimeout 執行時,i 已經變成 4 了。
✅ 正確的寫法 (使用 let)
* let 是區塊作用域,每次迭代都會產生一個新的 i,並被當前的閉包「黏」住。
總結:閉包的哲學
閉包不是什麼高深莫測的技巧,它是 JavaScript 為了讓函式變得「有生命」而設計的機制。理解了閉包,你就能從單純的「寫 Code」進階到「設計模式」,寫出更優雅、更具防護性的程式碼。