管理杂谈OA答疑ERP答疑教程搜索

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


问题代码

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

function f() {

    for(var i = 0; i < 10; i++) {

        setTimeout(() => {

            console.log('i=', i)

        });

    }

}


f();

输出结果:

i= 10

i= 10

i= 10

...(共 10 次)

执行过程详解

第一步:var 变量的作用域

for(var i = 0; i < 10; i())

    ↑

    └── var 声明的变量是函数作用域

        整个函数 f 内都能访问这个 i

第二步:循环执行过程

代码高亮:

循环次数     i 的值    循环条件 (i < 10)

---------------------------------------

第 1 次      0         ✓ 通过

第 2 次      1         ✓ 通过

...

第 10 次     9         ✓ 通过

             10        ✗ 不通过,循环结束


循环结束后:i = 10

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

for(var i = 0; i < 10; i++) {

    setTimeout(() => {

        console.log('i=', i)  // ← 所有回调共享同一个 i

    });

}

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

第四步:异步执行时序


时间轴:

─────────────────────────────────────────

| 同步执行阶段        | 异步执行阶段        |

─────────────────────────────────────────

for 循环完成      setTimeout 回调执行

i 递增到 10       读取 i 的值(此时 i=10)

                  输出 10 次 i=10

─────────────────────────────────────────

核心原因

三个关键点

  1. var 是函数作用域

    • 不是块级作用域

    • 整个函数内只有一个 i 变量

  2. 闭包共享变量

    • 10 个箭头函数都引用同一个 i

    • 不是创建 10 个独立的 i 副本

  3. setTimeout 异步执行

    • 回调函数放入任务队列延迟执行

    • 执行时循环已结束,i 已经是 10

图示理解:

变量 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(推荐)✨

代码高亮:

function f() {

    for(let i = 0; i < 10; i++) {

        setTimeout(() => {

            console.log('i=', i)

        });

    }

}

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

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

方案 2:IIFE 立即执行函数

function f() {

    for(var i = 0; i < 10; i++) {

        (function(j) {

            setTimeout(() => {

                console.log('i=', j)

            });

        })(i);

    }

}

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


方案 3:传递参数给 setTimeout

function f() {

    for(var i = 0; i < 10; i++) {

        setTimeout((j) => {

            console.log('i=', j)

        }, 0, i);

    }

}

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

知识点总结

一句话总结

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

 

参考文章:原文链接


更多精彩文章浏览...
点击右上角图标分享到朋友圈
官方网站:http://www.clicksun.cn
咨询热线:400-186-1886
服务邮箱:service@clicksun.cn