setTimeout + for loop + closure 核心介紹入門教學筆記 | 學習筆記
· 閱讀時間約 2 分鐘
一、var 和 let 的作用域差異
| 宣告方式 | 作用域 | 是否有暫時性死區(TDZ) |
|---|---|---|
var | 函式作用域 | 否 |
let | 區塊作用域 | 是 |
const | 區塊作用域 | 是 |
var宣告的變數在整個函式內都可存取let則只在所在區塊{}中有效
二、閉包(Closure)
閉包是指「函式能記住它被定義時的作用域,即使在外部執行也能存取當時的變數」。
function outer() {
let count = 0;
return function inner() {
console.log(count++);
};
}
const fn = outer();
fn(); // 0
fn(); // 1
這個例子中,inner 函式記住了 outer 中的 count 變數。
三、事件迴圈與 setTimeout
console.log("start");
setTimeout(() => {
console.log("timeout");
}, 0);
console.log("end");
輸出順序:
start
end
timeout
原因是 setTimeout 是非同步的,它會被放入「事件佇列」(event queue)中,等主程式執行完後才會被處理。
四、面試陷阱:var 與 setTimeout 搭配
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
這段程式的輸出是:
3
3
3
因為:
var沒有區塊作用域,所有的setTimeout都引用同一個i- 當
setTimeout執行時,迴圈已跑完,i已是 3
五、正確解法:使用 let
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
輸出為:
0
1
2
因為 let 有區塊作用域,每次迴圈都建立新的 i,閉包會記住各自的值。
六、另一個解法:IIFE(立即執行函式)
若必須用 var,可以用 IIFE 綁定每次的 i:
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => {
console.log(j);
}, 1000);
})(i);
}
這樣也會正確輸出:
0
1
2
七、總結
var是函式作用域,容易被非同步邏輯影響let是區塊作用域,與閉包搭配可解決常見陷阱setTimeout是非同步,會延遲執行- 通常面試會結合這些概念出題,考你對 JavaScript 執行流程、作用域與閉包的理解