2026/11/5 6:46:36

闭包之循环中捕获正确的值

闭包之循环中捕获正确的值 先来看下面这几段 JavaScript 代码它们执行后分别会输出什么其中哪些能正常输出 0、1、2// 1、var 陷阱 for (var i 0; i 3; i) { setTimeout(() { console.log(i); }, 100); } // 2、 IIFE 闭包 for (var i 0; i 3; i) { (function(index) { setTimeout(() { console.log(index); }, 100); })(i); } // 3、 let 块级作用域 for (let i 0; i 3; i) { setTimeout(() { console.log(i); }, 100); } // 4、const 传统 for for (const i 0; i 3; i) { setTimeout(() { console.log(i); }, 100); } // 5、const for...of for (const i of [0, 1, 2]) { setTimeout(() { console.log(i); }, 100); }答案↓只有第 2、3、5 段能正常输出 0、1、2你答对了吗第 1 段执行后输出 3、3、3第 4 段则直接语法报错只输出 0。为什么会这样呢从零到精通~~来理解吧一、先看以下代码var i; for (i 0; i 3; i) { console.log(i); // ← 当场打印 } // 输出0, 1, 2 ✅ // ----------------------------------------------------------------- // 块级作用域let 和 const 只在 {} 内部有效 function blockScope() { { let blockVar 我只在这里; const blockConst 我也只在这里; console.log(blockVar); // ✅ 可以访问 } // console.log(blockVar); // ❌ 编译错误找不到变量 } // 对比 var没有块级作用域 function noBlockScope() { { var x 我会逃逸出去; } console.log(x); // ✅ 居然能访问这就是问题所在 }这个你应该很容易理解同步版本会立即执行因此依次输出 0、1、2。没有问题每次打印时 i 恰好就是当时的值。二、那代码改成异步后呢var i; for (i 0; i 3; i) { setTimeout(() { console.log(i); // ← 过 3 秒才打印 }, 3000); } // 输出3, 3, 3 ❌ // 把这里的var 改成 let for (let i 0; i 3; i) { setTimeout(() { console.log(i); }, 3000); } // 输出0, 1, 2 ✅那么问题来了为什么用 let 就能正常打印而用 var 却不行呢let 的情况 var 的情况 ┌───────────────┐ ┌───────────────┐ │ 块1: i 0 │ │ │ │ setTimeout() │ │ var i │ └───────────────┘ │ (共用一个) │ ┌───────────────┐ │ │ │ 块2: i 1 │ │ │ │ setTimeout() │ │ │ └───────────────┘ │ │ ┌──────────────- │ │ │ 块3: i 2 │ │ │ │ setTimeout() │ │ │ └───────────────┘ └───────────────┘ 3 个独立的 i 只有 1 个 i用一张图说明let 在 for 循环里JS 引擎自动给每一圈循环 套了一个独立的 {} 块 相当于每次循环都重新声明了一遍 i 。所以 3 个回调各看各的 i 互不干扰。而var 的i只有1个3次循环都在改同一个i。1、let循环执行时js引擎实际干的事// 第 1 次循环 { let i 0; setTimeout(() console.log(i), 3000); } // 第 2 次循环 { let i 1; setTimeout(() console.log(i), 3000); } // 第 3 次循环 { let i 2; setTimeout(() console.log(i), 3000); }2、var循环执行时js引擎却是如下var i; // ← 只有一个在循环外面 i 0; setTimeout(() console.log(i), 3000); i 1; setTimeout(() console.log(i), 3000); i 2; setTimeout(() console.log(i), 3000); i 3; // 循环结束后 i 变成 33、那如果就是要用var呢怎么修改代码才能打印出 0,1,2是不是在执行的时候让函数立即执行ImmediatelyInvokedFunctionExpression就行了呢那么就引入了IIFE 闭包两个概念的组合了。(function() { // 代码... })(); // 注意这个 ()定义完立刻调用 // 解释 // fn() 执行fn函数 // 这里 fn 就是 function() {} // 也就是立即执行了 // 再看文章最开始的代码 // 2、 IIFE 闭包 for (var i 0; i 3; i) { (function(index) { setTimeout(() { console.log(index); }, 100); })(i); } // 理解了吧 立即执行fn(i) 就是执行 i是实参index是形参 // 这里i立即传给函数了所以 i是0 -》1 -》 2 function(index) { setTimeout(() { console.log(index); }, 100); }就像你写了个匿名函数不等别人来调当场就执行闭包 函数等“记住”并访问它所在的作用域即使函数在外面跑。组合起来是什么用 IIFE 创建一个 独立的私有作用域 里面的函数引用了外部变量就形成了 IIFE 闭包。最经典的例子// 用 IIFE 闭包实现计数器不污染全局变量 const counter (function() { let count 0; // 私有变量外面碰不到 return { increment() { count; return count; }, reset() { count 0; } }; })(); // ← 立刻执行 counter.increment(); // 1 counter.increment(); // 2 // count 变量永远是 0 吗不它被闭包记住了大白话类比概念类比IIFE你开了一间旅馆房创建了一个独立空间 开完立刻就进去 立即执行闭包你在房间里写了张纸条记着房号退房后纸条还在变量被记住了IIFE闭包开了房 → 立刻进去 → 做了些事 → 走的时候把房间号记在小本本上带走 → 下次来还能接着用实际用途1. 封装私有变量 — 不让外部直接访问内部状态2. 避免命名冲突 — 每个 IIFE 都是独立的小空间3. 循环绑定事件 — 经典的 for 循环 setTimeout 问题简单说 IIFE 是手段闭包是结果 组合起来就是「用一个一次性的函数创建出有持久记忆的私有空间」码字不易点个赞再走吧~~~