閉包是一種資料結構,包含函式以及該函式被宣告時所建立的環境組合,因此當函式是在其宣告的語彙範疇之外執行時也能正常運作。
在說閉包以前先來複習範圍練
function outside() {
let a = 1;
function inside() {
console.log(a);
}
inside();
}
outside(); //1
在inside()
這個函示裡是沒有 a 變數,因為沒有他會向外尋找,在上一層找到 a 變數就停止,假設在上一層也沒有就會再往外一層去做尋找,直到在全域裡也找不到,就會回傳 not defined。
var count = 0;
function number() {
return ++count;
}
console.log(number());
console.log(number());
console.log(number());
當我想要累加時會用一個全域變數來儲存,但當時用 var 就有可能會影響到其他程式碼,並造成錯誤。 如果我用 let,因為 let 是 block scope{},出去{}後就會消失數字也不會累加上去,永遠都會是1。
function number() {
let count = 0;
function times() {
return ++count;
}
return times;
}
let witch = number();
let witch2 = number();
console.log(witch()); //1
console.log(witch()); //2
console.log(witch()); //3
console.log(witch2()); //1
console.log(witch2()); //2
像這樣使用閉包就算不用 let 宣告變數,也不用擔心 var 變數會污染到,不是這個函式的其他程式碼,因為範圍練不在上一層找不到,所以不會影響。而且 let 變數也因為閉包而不會消失,而是可以繼續累加,使用閉包還可以使不同物件的累加不會相互影響。
function number() {
let count = 0;
return () => ++count;
}
let witch = number();
let witch2 = number();
console.log(witch()); //1
console.log(witch()); //2
console.log(witch()); //3
console.log(witch2()); //1
console.log(witch2()); //2
還可以搭配箭頭函示使程式碼更簡短。
同樣的函式let
與var
的會產生不同的結果
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 3,3,3
}, i * 1000);
}
因為 var 是全域變數,是屬於 function socpe,這個變數會一直在,所以不會產生閉包,要等 stack 清空排隊的資料才會上去,等到時候 i 已變成 3。
for (let i = 0; i < 3; i++) {
//Closure 閉包
setTimeout(() => {
console.log(i); //0,1,2
}, i * 1000);
}
因為 i 是用 let 宣告是屬於 block socpe 所以如果等到迴圈跑完,我 i 都不見了,為了防止 i 不見會把 i 包起來一起跑到 web apis 排隊印出。