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

为何需用IIFE创建新作用域?两类替代写法失效原因解析

来自《You Don't Know JS》:

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

输出结果为:

6 6 6 6 6

但使用如下IIFE写法:

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

输出结果为:

1 2 3 4 5

我的问题:为何以下两种写法无法达到IIFE示例的效果?在我看来它们都包含函数声明与新变量j,难道不会创建绑定特定i值的词法作用域吗?

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

以及

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

解答:为什么你的两种写法没法捕获循环的i值?

咱们先把核心问题拆解清楚:var声明的变量没有块级作用域,而且setTimeout的回调是异步执行的——等这些回调真正跑起来的时候,循环早已经跑完了,i的值已经变成6了。

先看你用来对比的IIFE写法:每一轮循环都会立即执行一个自执行函数,这个函数会创建一个自己的词法作用域,把当前循环的i值赋值给作用域里的j。因为IIFE是每轮循环同步执行的,所以每一个j都会绑定当前那一轮的i值,等后面setTimeout回调执行时,它访问的是自己所在IIFE作用域里的j,自然能拿到1-5。

现在看你的两种写法问题出在哪:

第一种写法:回调里声明j

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

这里的var j = i是写在timer函数里的,但这个函数是等1-5秒后才执行的。等它执行的时候,循环已经结束,全局/函数作用域里的i已经是6了。所以每一次执行timer,j都会被赋值为当前的i(也就是6),自然输出全是6。你并没有在循环的每一轮把i的值“固定”下来,只是在回调执行的时候才去拿i的最新值。

第二种写法:循环里声明timer函数

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

这里有两个关键问题:

  1. 在var的作用域规则下,函数声明timer会被提升到当前作用域的顶部,所以每一轮循环的timer其实是同一个函数(后面的声明会覆盖前面的,但本质上都是指向同一个函数引用)。
  2. 同样的,timer函数里的j = i也是在回调执行的时候才赋值,这时候循环已经结束,i的值是6,所以输出还是全6。

简单总结:IIFE的核心是在循环每一轮同步捕获当前的i值,把它锁在自己的作用域里;而你的两种写法都是在异步回调执行时才去读取i的当前值,这时候i已经变成6了。

如果想达到同样的效果,除了IIFE,你也可以用ES6的let声明i,因为let有块级作用域,每一轮循环都会创建一个新的i变量,回调会绑定当前轮的i:

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

这样也能输出1-5的结果。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:38:12