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= 0 到 i= 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)

相关推荐
鬼手点金7 小时前
nanoGPT/nanochat 能做出 DeepSeek/Qwen 这样的大模型吗?
服务器·前端·javascript·bug·openclaw
软件技术NINI8 小时前
美妆商城html+Css+js 4页
javascript·css·html
蛋蛋的就会顺顺的8 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
码艺-Alimjan9 小时前
Tauri 源码备份 · 打包 & 解压 总结,源码收集之路
前端·javascript·vue.js
DevUp11 小时前
标着 JavaScript 的 Wiki 与知识库:既不是 CMS 也不是 SSG(番外三)
javascript·node.js·cms
kyriewen12 小时前
我上手了 Vite+ 1.0:Node 不用自己装,lint 却翻车了
前端·javascript·程序员
鹏多多13 小时前
前端是如何死去的?| 完整时间线
前端·javascript·程序员
古夕13 小时前
iframe 授权失败时露出 60000 JSON 的复盘
前端·javascript
程序员Sunday13 小时前
ES Module 和 CommonJS 有什么区别?循环依赖时会发生什么?
前端·javascript·面试
有味道的男人14 小时前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript