You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何setTimeout在for循环中表现异常?为何弹窗显示6而非5?

为什么setTimeout里的alert总是弹出6而不是5?

这是个经典的JavaScript异步机制+变量作用域问题,我来给你拆解明白~

首先先对比下两段代码的差异:

第一段同步执行的循环:

for (i = 1; i <= 5; i++) { alert(i) }

这里alert是同步操作,每一次循环迭代都会立刻执行,直接拿到当前i的数值,所以依次弹出1到5,完全符合预期。

而第二段带setTimeout的循环:

for (i = 1; i <= 5; i++) { setTimeout(function(){ alert(i) }); }

问题出在两个核心点上:

1. 异步回调的执行时机

setTimeout的回调函数不会立刻执行,它会被浏览器放到任务队列中,只有当当前所有的同步代码(也就是整个for循环)完全执行完毕后,才会依次执行队列里的回调函数。

2. 共享的变量引用

这里的i是全局变量(哪怕你用var声明,在全局环境下也是全局作用域),所有五个回调函数都共享同一个i的引用——它们并不是保存了循环时i的快照,而是在执行时才去读取i的当前值。

循环的完整执行流程

我们一步步看循环到底做了什么:

  • 第1次迭代:i=1,调用setTimeout把回调放进队列,然后执行i++,i变成2
  • 第2次迭代:i=2,放回调,i++变3
  • ...
  • 第5次迭代:i=5,放回调,然后执行i++,i变成6
  • 这时候检查循环条件i<=5,6>5,循环终止

等循环结束后,浏览器才开始处理任务队列里的五个回调。这时候去读取i的值,它已经是6了,所以每个回调弹出的都是6。

那为什么不是5?因为循环的最后一步是:当i=5时,执行完循环体里的setTimeout后,会先执行i++让i变成6,再去判断循环条件是否成立——这时候条件不满足,循环才停止。所以循环结束时i的最终值是6,而非5。

怎么修复这个问题?

如果想让回调弹出1到5,有几种常用方法:

  • 用let声明i:利用ES6的块级作用域,每次循环迭代都会创建一个新的i变量,回调会绑定当前迭代的i:
    for (let i = 1; i <= 5; i++) { setTimeout(function(){ alert(i) }); }
    
  • 用立即执行函数(IIFE)创建闭包:把当前i的值作为参数传入IIFE,保存为独立的变量:
    for (i = 1; i <= 5; i++) {
      (function(num) {
        setTimeout(function(){ alert(num) });
      })(i)
    }
    
  • 用setTimeout的第三个参数传值:直接把i作为参数传给回调:
    for (i = 1; i <= 5; i++) {
      setTimeout(function(num){ alert(num) }, 0, i)
    }
    

内容的提问来源于stack exchange,提问作者Dipesh rana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:41:17