for循环中var和let的区别

231 阅读2分钟

你提出的这两段代码非常经典,很好地展示了 JavaScript 中 varlet 在作用域和闭包行为上的关键区别


🔍 问题重现:

// 使用 var
for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  });
}

// 使用 let
for (let i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  });
}

我们来分别分析它们的输出结果和背后原理。


✅ 第一段:使用 var

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

❓ 输出什么?

5
5
5
5
5

🧠 原因解释:

  • var 是函数作用域(function-scoped),在全局或函数中声明的变量会提升并共享同一个作用域。
  • 这个循环中的 i 是一个全局变量(或者函数作用域变量)。
  • 所有的 setTimeout 回调函数都引用的是 同一个变量 i
  • setTimeout 真正执行时(事件循环之后),循环已经结束,此时 i 的值是 5
  • 所以所有的回调都打印出 5

✅ 第二段:使用 let

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

❓ 输出什么?

0
1
2
3
4

🧠 原因解释:

  • let 是块级作用域(block-scoped),在 {} 内部定义的变量只在该块内有效。
  • 更重要的是,在 for 循环中使用 let 声明变量时,JavaScript 会在每次迭代中创建一个新的绑定(即新的作用域)。
  • 每次循环体中创建的 setTimeout 回调函数都会捕获当前迭代中的 i
  • 所以每个回调函数打印出的是各自迭代中被“记住”的 i 值。

💡 形象比喻

你可以把这两个例子想象成:

使用 var使用 let
所有回调函数共用一盏灯(同一个 i 变量)每次循环都有一盏新灯(独立的 i
最后灯关了(i=5),所有人都看到黑每个人记住自己当时看到的灯光亮度

🛠️ 如果你想用 var 达到 let 的效果怎么办?

可以通过 IIFE(立即执行函数表达式) 来手动创建作用域:

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

这样也能输出:

0
1
2
3
4

因为 IIFE 给每次循环创建了一个新的作用域,并把当时的 i 值传进去保存下来。


✅ 总结对比表:

特性var ilet i
作用域函数/全局作用域块级作用域
每次循环是否创建新变量
setTimeout 捕获的值最终的 i(5)每次循环的当前 i
输出结果5 5 5 5 50 1 2 3 4
推荐使用❌ 不推荐用于异步回调✅ 推荐

如果你想了解更多关于作用域、闭包、事件循环等概念,也欢迎继续提问!😊