🔔 閉包就是內層函式可以取得外層函式作用域的變數
MDN文件:閉包
閉包是什麼?
閉包是 JavaScript 中的一種特性,它允許函式訪問其外部作用域中的變數,即使函式在外部作用域之外被執行。閉包是由函式及其相關的作用域鏈組成的。
以下範例,createCounter() 函式返回了一個內部函式,內部函式能夠訪問 createCounter() 中的 count 變數,就算 createCounter() 已經執行過了,還是可以訪問,這就是閉包的效果。
function createCounter() {
let count = 0;
return function() {
count += 1;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
閉包的運作
當一個函數在 JavaScript 中建立時,它帶有一個名為 [[Environment]] 的隱藏屬性,該屬性保留對其詞法環境(即定義該函數的位置)的引用,這種機制使 JavaScript 函數能夠「記住」它們的原始環境。
到現在還是沒有找到直接看 [[Environment]] 的方法,但是可以用 debugger 和 chrome 的開發者工具觀察函數內部的作用域和變量。
參考文章:[筆記]-JavaScript 閉包(Closure)是什麼?關於閉包的3件事、词法环境(Lexical Environment)

數據封裝和私有變量
在 JavaScript中,每創建函式,閉包就會在函式創建的同時被創建出來,作為函式內部與外部連接起來的一座橋樑。
任何閉包的使用場景都離不開這兩點:
私有方法可以增加函式的可用性,主要在回傳的函式中可以執行多種方法,只要在 return 的地方用物件的方式包裝起來就可以了!
function createCounter() {
let count = 0; // 建立私有變數
return {
increment: () => {
count++;
return count;
},
decrement: () => {
count--;
return count;
},
getCount: () => count
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
function storeMoney(initMoney = 1000) {
const myMoney = initMoney;
return {
increase: function(price) {
myMoney += price;
},
decrease: function(price) {
myMoney -= price;
},
value: function(price) {
return myMoney;
}
}
}
const kukuMoney = storeMoney(1000);
kukuMoney.increase(5000);
kukuMoney.decrease(1000);
console.log(kukuMoney.value()); // 5000
函數工廠
閉包可以透過不同的變數,讓他做相同的事情,也稱為函式工廠。
function makeMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
// 使用閉包分別產生兩個作用域
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
🔔 使用閉包需要注意:
如果不是某些特定任務需要使用閉包,在函式中創建函式是不明智的,因為閉包在處理速度和記憶體消耗方面對腳本效能有負面影響,簡單來說,閉包使用過程需要保留對外部作用域變數的參考,所以會增加記憶體使用量。
🔔 有空可以看一下的相關資料:
You Don't Know JS Yet: Scope & Closures - 2nd Edition
在第 4 天:函式 function提過函數作用域與閉包,這裡就要來好好說什麼是作用域,要做什麼的呢?
作用域鏈是指在執行上下文中,JavaScript 引擎如何查找變數和函式的過程,一個函式內部訪問變數時,JavaScript 引擎會按照以下順序查找變數:
這樣的查找過程形成了一個鏈條,稱為作用域鏈。每個作用域都可以訪問其自身的變數,以及其外部作用域中的變數,直到全局作用域為止。
以下範例:inner 函式能夠訪問 innerVar、outerVar 和 globalVar,這是因為它們依次位於 inner 函式的作用域鏈中。
const globalVar = 'global';
function outer() {
const outerVar = 'outer';
function inner() {
const innerVar = 'inner';
console.log(innerVar); // 'inner'
console.log(outerVar); // 'outer'
console.log(globalVar); // 'global'
}
inner();
}
outer();
以下是面試曾經被問過類似的題目,請說明 console.log 出來陸續是什麼呢?為什麼?
function getArray() {
const arr = [];
for (var i = 0; i < 3; i++) {
arr.push(function () {
console.log(i);
});
}
return arr
}
const fn = getArray();
fn[0]();
fn[1]();
fn[2]();

連續輸出都是 3 因為是執行外層函式作用域的變數。
其實實戰中,都是 push 變數什麼的,push 一個函數很少見,也不知道面試主管想要做什麼,所以往上看到 var 大概猜到他想要考 ES6 block scope...
外層作用域變數會隨著閉包概念不斷的去控制他,所以執行 for 迴圈的時候已經不斷的被累加,所以每次執行閉包的時候,只會取到目前作用域下的數字。
再說簡單些因為他是用 var,第 2 天:基本語法和資料類型的宣告變數 - var、let、const (let 和 const 在 ES6 出現)提過 var 只有 function scope,而不是 block scope,也就是在整個 getArray() 裡面都是可見的,而不是只有在定義的 for 循環塊內,所以當 for 循環完成時,i 的值是 3。
🤔 那跟閉包有什麼關係?var 在整個函數內部都是可見的,所以所有閉包都引用同一個變量,console.log 出來的 i 就都會是最後一個 3。
🤔 怎麼印出 0, 1, 2?
限制作用域!for 循環迭代時,IIFE 都會被立即呼叫,然後創建一個新的作用域來保存 i 的當前值,所以作用域是獨立的!
function getArray() {
const arr = [];
for (var i = 0; i < 3; i++) {
arr.push(((i) => () => console.log(i))(i));
}
return arr;
}
const fn = getArray();
fn[0]();
fn[1]();
fn[2]();
let 、const:for 迴圈的變數作用域就只會在整個迴圈中,不會受到外層影響,外層也無法取得!
function getArr() {
const arr = [];
for (let i = 0; i < 3; i++) {
arr.push(() => console.log(i));
}
return arr
}
const fn = getArr();
fn[0]();
fn[1]();
fn[2]();