JavaScript 闭包经典问题:为什么输出 10 次 i=10

JavaScript 闭包经典问题:为什么输出 10 次 i=10

问题代码

先观察以下代码,思考输出结果:

javascript 复制代码
function f() {
    for(var i = 0; i < 10; i++) {
        setTimeout(() => {
            console.log('i=', i)
        });
    }
}

f();

输出结果:

ini 复制代码
i= 10
i= 10
i= 10
...(共 10 次)

执行过程详解

第一步:var 变量的作用域

css 复制代码
for(var i = 0; i < 10; i())
    ↑
    └── var 声明的变量是函数作用域
        整个函数 f 内都能访问这个 i

第二步:循环执行过程

markdown 复制代码
循环次数     i 的值    循环条件 (i < 10)
---------------------------------------
第 1 次      0         ✓ 通过
第 2 次      1         ✓ 通过
...
第 10 次     9         ✓ 通过
             10        ✗ 不通过,循环结束

循环结束后:i = 10

第三步:创建 10 个回调函数

css 复制代码
for(var i = 0; i < 10; i++) {
    setTimeout(() => {
        console.log('i=', i)  // ← 所有回调共享同一个 i
    });
}

每次循环创建一个箭头函数,都通过闭包引用变量 i

第四步:异步执行时序

css 复制代码
时间轴:
─────────────────────────────────────────
| 同步执行阶段        | 异步执行阶段        |
─────────────────────────────────────────
for 循环完成      setTimeout 回调执行
i 递增到 10       读取 i 的值(此时 i=10)
                  输出 10 次 i=10
─────────────────────────────────────────

核心原因

三个关键点

  1. var 是函数作用域

    • 不是块级作用域
    • 整个函数内只有一个 i 变量
  2. 闭包共享变量

    • 10 个箭头函数都引用同一个 i
    • 不是创建 10 个独立的 i 副本
  3. setTimeout 异步执行

    • 回调函数放入任务队列延迟执行
    • 执行时循环已结束,i 已经是 10

图示理解

less 复制代码
变量 i 的生命周期:
─────────────────────────────→ 时间
     0 1 2 3 4 5 6 7 8 9  10
     └───┬───┘ └───┬───┘
         │                │
     同步循环执行        循环结束
                        i=10

回调函数 1:  ────────────────────→ 读取 i (10)
回调函数 2:  ────────────────────→ 读取 i (10)
...
回调函数 10: ────────────────────→ 读取 i (10)

解决方案

方案 1:使用 let(推荐)✨

javascript 复制代码
function f() {
    for(let i = 0; i < 10; i++) {
        setTimeout(() => {
            console.log('i=', i)
        });
    }
}

原理: let 是块级作用域,每次循环创建新的 i 绑定

输出: i= 0i= 9 各一次


方案 2:IIFE 立即执行函数

javascript 复制代码
function f() {
    for(var i = 0; i < 10; i++) {
        (function(j) {
            setTimeout(() => {
                console.log('i=', j)
            });
        })(i);
    }
}

原理: 通过函数参数保存每次循环的 i 值


方案 3:传递参数给 setTimeout

javascript 复制代码
function f() {
    for(var i = 0; i < 10; i++) {
        setTimeout((j) => {
            console.log('i=', j)
        }, 0, i);
    }
}

原理: setTimeout 的第三个参数会传递给回调函数


知识点总结

概念 说明
var 作用域 函数作用域,非块级作用域
let 作用域 块级作用域,每次循环创建新绑定
闭包 函数可以访问其声明时所在作用域的变量
异步 setTimeout 的回调会延迟执行
共享引用 同一作用域的闭包引用同一个变量

一句话总结

var 的函数作用域 + 闭包共享变量 + setTimeout 异步执行 = 所有回调读取到循环结束后的同一个 i 值(10)

相关推荐
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄1 天前
JavaScript 隐式全局变量解析
javascript
沙蒿同学1 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端
paopaokaka_luck1 天前
智慧社区综合服务小程序(人脸识别、AI问答、Echarts图形化分析)
前端·javascript·spring boot·spring·数据分析·echarts
Hilaku1 天前
为什么死磕 1px 的团队,用户体验反而更差?
前端·javascript·程序员
陈拉斯1 天前
给公司年会写了个大屏抽奖系统:动画在前端跑,凭什么说结果没被改?
javascript
yzy852 天前
Vue中下载或打开文件的JavaScript方法
前端·javascript·vue
用户298698530142 天前
在 React 中用 JavaScript 将 Word 转换为文本
前端·javascript·react.js
李少兄2 天前
深入解析 JavaScript 中的 `document` 对象
javascript