先来看下面这几段 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 是手段,闭包是结果 ,组合起来就是「用一个一次性的函数,创建出有持久记忆的私有空间」
码字不易,点个赞再走吧~~~