🔔 閉包就是內層函式可以取得外層函式作用域的變數
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]();