被闭包啪啪啪的打脸之 闭包的错误使用

作者:HerryLo

原文永久链接: https://github.com/AttemptWeb/Record...

尴尬了,遇到了一个闭包的问题,然后我说错了答案,装逼失败了,之前我以为自己完全理解了闭包,现在发现其实并没有,赶紧翻书找答案-ing。

看下面的代码,在循环中向数组导入函数, 希望可以打印 0,1,2 :

function func() {
    var arr = [];
    for(var i = 0;i<3;i++){
        arr.push(()=> {
            console.log(i);
        })
    }
    return arr
}
var result = func();
result.forEach((item)=> {
    item();
})
<!-- 打印信息 三个3 -->
// 3

发现打印的都是3,原因是匿名函数中的i共享了同一个词法作用域 。当变量数组调用匿名函数时, var 声明的变量不存在块级作用域, i 的值已经指向了for循环 i 的最后一项。

解决上面的问题 使用let或者闭包可以解决上面的问题,解决代码如下:

<!-- 方案一 -->
// 使用let 声明变量
function func() {
    var arr = [];
    for(let i = 0;i<3;i++){
        arr.push(()=> {
            console.log(i);
        })
    }
    return arr
}
var result = func();
result.forEach((item)=> {
    item();
})

<!-- 方案二 -->
// 使用闭包
function func() {
    var arr = [];
    for(var i = 0;i<3;i++){
        (function(){
            arr.push(()=> {
                console.log(i);
            })
        })()
    }
    return arr
}
var result = func();
result.forEach((item)=> {
    item();
})

以为已经解决了, 没想出了其他问题!可以运行一下上面的方案一和方案二, 你会发现方案二的结果是打印出了三个3,WT?不是应该打印0、1、2, 怎么没有?

闭包的作用域链

方案一中当然是没有问题的,使用let解决作用域问题。在方案二中, 使用闭包解决变量i的作用域问题,但是好像闭包失效了。

在方案二中闭包的作用是变量私有化,保存闭包作用域链,变量i不被销毁。对于闭包作用域不了解的可以 查看冴羽的 JavaScript深入之执行上下文 和 JavaScript深入之闭包。而方案二中的结果却不是我们想要的,究其原因,是我对闭包作用域不理解导致的。

<!-- 方案三 -->
function func() {
    var arr = [];
    for(var i = 0;i<3;i++){
        (function(i){
            arr.push(()=> {
                console.log(i);
            })
        })(i)
    }
    return arr
}
var result = func();
result.forEach((item)=> {
    item();
})

以上就是解决方案,将i加在匿名函数参数就解决了方案二的问题。

下面来细说一下是怎么回事: 为了方便描述,我将 自运行的匿名函数 简称为 fn1, 而arr中的回调匿名函数 简称为 fn2。当然arr中的的三个函数分别是三个不同的 fn2函数。

函数作用域链

fn2函数作用域链 : {
fn2函数变量和参数 , fn1函数变量&&参数 , func函数变量&&参数 , 全局作用域变量
}

当调用func函数, 函数fn1自运行,变量arr被注入三个fn2函数,同时arr被return出来。此时形成了闭包作用域链。

数组中的fn2函数运行向上不断查找i,fn1函数和fn2函数中都不存在i,直到找到func函数变量i,此时由于i是var声明的,不存在块级作用域,三个fn2函数共享i。(其实这个地方有点和最开头的解释重复了,不过这里出现的新东西可能就是闭包作用域链了)。

在正常函数调用后,作用域链被销毁,但当存在闭包时,对应的作用域链会被保存。 arr中的fn2函数作用域,就基本形成一个作用域链,作用域链是单向的,内部向外部查找,由下向上查找。作用域链中会保存局部变量、全局变量、函数参数。

比较方案二 与 方案三

// 方案二
function func() {
    var arr = [];
    for(var i = 0;i<3;i++){
        (function(){
            arr.push(()=> {
                console.log(i);
            })
        })()
    }
    return arr
}
var result = func();
result.forEach((item)=> {
    item();
})

现在再来看 方案二: 遍历result调用时,arr中的fn2函数会先查找自身函数作用域,不存在i那么就向上继续查找,找到func函数下的变量i,将i打印出来,但此时func函数中的变量i已经等于3了, 由于for循环三次,arr中有三个fn2函数, 同上, 所以打印了三次3。

// 方案三
function func() {
    var arr = [];
    for(var i = 0;i<3;i++){
        (function(i){
            arr.push(()=> {
                console.log(i);
            })
        })(i)
    }
    return arr
}
var result = func();
result.forEach((item)=> {
    item();
})

而在 方案三: 遍历result调用时, arr中的fn2函数会先查找自身函数作用域,不存在i那么就向上继续查找,找到fn1函数中参数i, 将i打印出来。 由于存在三个匿名函数,所以函数参数分别是0、1、2。

结尾: 其实说到这里,基本可以了解,方案二中的问题,其实就是闭包作用域链的问题,当形成闭包时,闭包涉及到的作用域链会被保存。如果真正的了解了闭包,绝对不会遇见像我这样的问题,算是给我自己上了一课。写的不好的地方希望大家可以指出,下面是参考的文章链接。

(如果上面的内容引起你的不适感,可以参考 冴羽的blog )

参考文章:

MDN 闭包

JavaScript深入之执行上下文

JavaScript深入之闭包

ps: 顺便推一下自己的个人公众号:Yopai,有兴趣的可以关注,每周不定期更新,分享可以增加世界的快乐

posted @   herryLo  阅读(308)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示