Phoenix's Blog

JavaScript | Closure

2019-09-06

[JavaScript] - Closure

先來看看 wiki 上對 Closure 的解釋:

在電腦科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函式閉包(function closures),是參照了自由變數的函式。這個被參照的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函式和與其相關的參照環境組合而成的實體。閉包在執行時可以有多個實體,不同的參照環境和相同的函式組合可以產生不同的實體。

直接來看個常見的例子:

1
2
3
4
5
6
var btn = document.querySelectorAll('button')
for(var i=0; i<=2; i++) {
btn[i].addEventListener('click', function() {
console.log(i);
})
}

畫面上有3個按鈕,我希望點擊第一個按鈕時能出現 0,第二個物件的時候 1,第三個出現2…以程式來看好像沒什麼問題,但依序點擊後,實際印出來卻是:

1
2
3
3
3
3

但其實回頭再看一次程式,當我們依序點擊每個按鈕 btn[0]、btn[1]、btn[2] 時,按鈕監聽到點擊事件後要呼叫 function 時,i 最後已經是 3 ,所以點擊出來是3:

1
2
3
4
5
for(var i=0; i<=2; i++) {
btn[i].addEventListener('click', function() {
console.log(i);
})
}

這時我們可以透過閉包的方式改寫一下:

1
2
3
4
5
6
7
8
9
function getId(id){
return function(){
console.log(id);
}
}

for(var i=0; i<=2; i++) {
btn[i].addEventListener('click',getId(i));
}

新增一個 getId 的 function ,讓每個 btn 都各自有 getId function ,當我點擊事件發生後,各自的 getId 會 return 裡面的 function ,而這個 function 的作用域只會在 getId 裡 不會被外層影響到

而在 ES6 中有個 let , let 是屬於塊級作用域( block scoped ),塊級作用域就是限縮在 { }之中,以上面的例子,我們用 let 改寫:

1
2
3
4
5
for(let i=0; i<=2; i++) {
btn[i].addEventListener('click',function({
console.log(i)
});
}

透過 let 的特性,當迴圈每執行一次,就會產生一個新的作用域,就會像下方這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
let i=0;
btn[i].addEventListener('click',function({
console.log(i)
});
}
{
let i=1;
btn[i].addEventListener('click',function({
console.log(i)
});
}
.
.

因此就可以避免 i 這個變數,不會因為全域變數的關係不斷的被修改.

參考:
深入淺出瞭解 JavaScript 閉包(closure)
所有的函式都是閉包:談 JS 中的作用域與 Closure

tags: JavaScript Closure