# setTimeout與循環閉包的示例分析
## 引言
在JavaScript異步編程中,`setTimeout`與循環閉包的組合常會引發令人困惑的現象。本文將通過多個代碼示例,深入分析這種組合產生的問題及其解決方案。
## 一、基礎概念回顧
### 1.1 setTimeout的工作原理
```javascript
setTimeout(callback, delay)
callback
函數加入任務隊列delay
毫秒后執行(最小延遲4ms)function outer() {
let count = 0;
return function inner() {
return ++count;
}
}
for (var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
// 輸出:6 6 6 6 6(每秒一個)
i
最終值為6i
的引用i
值// 實際執行等價于
var i;
for (i = 1; i <= 5; i++) {
// ...
}
時間點 | i值 | 內存狀態 |
---|---|---|
t=0ms | 6 | 5個定時器已注冊 |
t=1000ms | 6 | 第一個回調執行 |
… | … | … |
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, j * 1000);
})(i);
}
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
for (var i = 1; i <= 5; i++) {
setTimeout(function(j) {
console.log(j);
}, i * 1000, i);
}
for (var i = 1; i <= 3; i++) {
for (var j = 1; j <= 3; j++) {
setTimeout(function() {
console.log(i, j);
}, (i * 3 + j) * 100);
}
}
// 輸出:4 4(共9次)
解決方案:
for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
setTimeout(function() {
console.log(i, j);
}, (i * 3 + j) * 100);
}
}
const tasks = [];
for (var i = 0; i < 5; i++) {
tasks.push(() => {
return new Promise(res => {
setTimeout(() => res(i), 1000);
});
});
}
// 所有Promise都resolve(5)
優化方案:
const tasks = Array(5).fill().map((_, i) =>
() => new Promise(res => {
setTimeout(() => res(i + 1), 1000 * (i + 1));
})
);
方案 | 每次迭代內存消耗 | 總內存消耗 |
---|---|---|
var | 1個閉包 | 低 |
IIFE | n個閉包 | 中 |
let | n個塊作用域 | 中 |
// 測試代碼示例
console.time('let');
for (let i = 0; i < 10000; i++) {
setTimeout(() => {}, 0);
}
console.timeEnd('let');
典型結果: - let方案:~120ms - IIFE方案:~150ms - var方案:~80ms(但結果錯誤)
let
+塊級作用域for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
while(true) {} // 阻塞測試
}, 1000);
}
async function process() {
for (let i = 1; i <= 5; i++) {
await new Promise(res => {
setTimeout(() => {
console.log(i);
res();
}, 1000);
});
}
}
通過本文分析可見,理解閉包與異步執行的交互機制至關重要。選擇適合的解決方案需要權衡: - 代碼可讀性 - 運行環境要求 - 性能需求 - 團隊協作約定
正確運用這些知識,可以避免常見的陷阱,編寫出更健壯的異步代碼。
附錄:完整測試代碼
// 所有方案的完整實現對比
const implementations = [
{
name: 'Problematic var',
code: () => {
for (var i = 1; i <= 5; i++) {
setTimeout(() => console.log('var:', i), i * 300);
}
}
},
{
name: 'IIFE Solution',
code: () => {
for (var i = 1; i <= 5; i++) {
(j => {
setTimeout(() => console.log('IIFE:', j), j * 300);
})(i);
}
}
},
{
name: 'let Solution',
code: () => {
for (let i = 1; i <= 5; i++) {
setTimeout(() => console.log('let:', i), i * 300);
}
}
}
];
implementations.forEach(impl => {
console.log(`\nRunning ${impl.name}:`);
impl.code();
});
”`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。