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)

相关推荐
To_OC5 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
小林ixn7 小时前
从零到一理解 React 父子组件通信:手写一个 Todo 应用带你彻底搞懂单向数据流
前端·javascript·react.js
窝子面9 小时前
手搓最简前后端协作-node
javascript·数据库
kyriewen10 小时前
我让AI给前端项目做了一次完整的Code Review——它和人类的差距,比我想的大得多
前端·javascript·ai编程
এ慕ོ冬℘゜10 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
sugar__salt11 小时前
Vue3 表单双向绑定与响应式核心 API 技术详解
前端·javascript·vue.js
SmartBoyW12 小时前
CSS弹性布局(Flexbox)学习笔记:从零开始搞懂弹性布局
前端·javascript
用户9385156350712 小时前
写了这么久 useState,你真的知道它在干什么吗?
前端·javascript
烬羽12 小时前
递归老写崩?一个"退回"公式,把回溯题变成填空题
javascript·深度学习·算法
浮江雾14 小时前
Flutter第十七节-----路由管理(3)
android·开发语言·前端·javascript·flutter·入门