91网首页-91网页版-91网在线观看-91网站免费观看-91网站永久视频-91网站在线播放

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

javascript篇:setTimeout遇上for循環:為什么總是輸出5?如何正確輸出0-4?

freeflydom
2025年6月4日 11:39 本文熱度 259

今天咱們來聊聊一個經典的面試題,也是很多新手容易踩坑的問題——在for循環中使用setTimeout。先看這段代碼:

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

你以為它會輸出0,1,2,3,4?太天真了!實際輸出是五個5!這是為什么?又該如何解決?且聽我慢慢道來~

一、為什么會這樣?——作用域與閉包的"陷阱"

這個現象背后隱藏著JavaScript的兩個重要特性:

  1. var沒有塊級作用域:在for循環中用var聲明的i實際上是函數作用域(或全局作用域)的
  2. 異步執行:setTimeout的回調函數會在循環結束后才執行

具體執行過程是這樣的:

  1. for循環瞬間執行完畢(同步代碼),i從0增加到5(當i=5時循環停止)
  2. 1秒后,5個setTimeout回調開始執行
  3. 此時它們訪問的都是同一個i,而i的值已經是5了
  4. 所以輸出了5個5

二、解決方案1:使用IIFE創建閉包

for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 1000);
  })(i);
}

原理

  • 立即執行函數(IIFE)為每次循環創建一個新作用域
  • 把當前的i值作為參數j傳入并"凍結"住
  • 每個setTimeout回調訪問的都是自己閉包中的j

三、解決方案2:使用let塊級作用域(ES6推薦)

for (let i =  0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

這是最優雅的解決方案!

  • let有塊級作用域,每次循環都會創建一個新的i
  • 相當于自動為我們創建了閉包
  • 代碼簡潔直觀,沒有魔法

四、解決方案3:利用setTimeout的第三個參數

for (var i = 0; i < 5; i++) {
  setTimeout(function(j) {
    console.log(j);
  }, 1000, i);
}

小技巧

  • setTimeout可以接受多個參數,第三個及以后的參數會作為回調函數的參數
  • 相當于瀏覽器幫我們做了參數綁定

五、解決方案4:用bind提前綁定參數

for (var i = 0; i < 5; i++) {
  setTimeout(function(j) {
    console.log(j);
  }.bind(null, i), 1000);
}

原理

  • Function.prototype.bind可以提前綁定參數
  • 第一個參數是this(這里不需要所以傳null)
  • 后續參數會作為綁定函數的參數

六、深入理解:為什么let能解決問題?

let在for循環中的行為很特殊:

  1. 每次迭代都會創建一個新的詞法環境(可以理解為新的作用域)
  2. 新的i會在這個環境中初始化,值為上一次迭代結束時的值
  3. 相當于自動為我們創建了閉包

可以近似理解為:

// 偽代碼,幫助理解let的行為
{
  let i = 0;
  setTimeout(function() { console.log(i); }, 1000);
}
{
  let i = 1;
  setTimeout(function() { console.log(i); }, 1000);
}
// ...以此類推

七、實際開發中的建議

  1. 默認使用let/const:告別var,擁抱塊級作用域
  2. 注意異步代碼的依賴關系:異步回調中使用循環變量時要特別小心
  3. 合理使用閉包:理解閉包的工作原理,但不要濫用
  4. 考慮代碼可讀性:有時候把異步邏輯提取成獨立函數會更清晰

八、舉一反三:類似的陷阱

這種問題不僅出現在setTimeout中,其他異步場景也會遇到:

// 事件監聽中的類似問題
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', function() {
    console.log(i); // 總是輸出buttons.length
  });
}
// 解決方案同樣適用
for (let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', function() {
    console.log(i); // 正確輸出對應的索引
  });
}

九、總結

  1. 問題根源:var的作用域 + 異步執行時機

  2. 解決方案

    • IIFE創建閉包(傳統方式)
    • 使用let(最推薦)
    • 利用setTimeout第三個參數
    • 使用bind綁定參數
  3. 最佳實踐:使用let/const避免這類問題

記住,在JavaScript中,同步代碼和異步代碼的執行時機是需要特別關注的重點。理解閉包和作用域,就能輕松應對這類問題。

轉自https://juejin.cn/post/7510587921788321832


該文章在 2025/6/4 11:48:19 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产全部理论 | 中文字幕亚洲精品 | 国产自2区 | 日韩中文有码高清 | 日本伦理 | 国产精品国产高清 | 欧美自拍无毒不卡 | 欧洲在线免费视频 | 欧美综合| 91偷拍精品一 | 美日韩午夜福利 | 日韩中文字幕网站 | 区二区欧美| 91福利电影网 | 91精彩视频| 国产精品偷伦视频 | 福利写真影院 | 精选国产911在线 | 精品福利一 | 国产噜噜噜精品免费 | 国产主播福利大全 | 人人鲁人| 国产a级自拍| 国产又粗又长的视频 | 国产精品成人观看视 | 日本高清精品一区 | 91精品区| 国产高清亚 | 国产午夜羞羞 | 国产一区自拍欧美 | 日韩成人午夜电影 | 乱伦国产欧美三级 | 精品国产免费 | 午夜视频在线瓜伦 | 女同一级毛 | 日韩精品在线播放 | 精品国产小说 | 九九国产中文字幕 | 91免费视频 | 精品国产2025 | 韩国三级hd中文 |