深入理解JavaScript闭包

定义

闭包的定义:

闭包是指那些能访问自由变量的函数。

自由变量:

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

所以我们可以理解为闭包是由 函数 + 自由变量 组成的。

换句话说,闭包就是函数和被创建的函数中的作用域对象的组合。

所以在理论上所有JavaScript函数都是闭包。

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
  2. 从实践角度:以下函数才算是闭包:
    • 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
    • 在代码中引用了自由变量

回顾

在上一篇《深入理解JavaScript执行上下文》文章中使用过一个例子:

var scope = 'global scope'
function checkscope(){
    var scope = 'local scope'
    function f(){
        return scope
    }
    return f
}
checkscope()()

这段代码的执行在上一篇文章有具体的分析,在这里我们可能会有一个疑问(分析可参考上一篇文章):

在f函数的执行上下文入栈之前,checkscope函数已经出栈,那么为什么还能读取scope的值呢?

其实我们看分析可以知道在f函数执行上下文中维护了一个作用域链

fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO]
}

就是因为这条作用域链的存在,才使得f函数在checkscope函数执行上下文销毁后仍能读取checkscopeContext.AO中的值,因为f函数与checkscopeContext.AO存在引用关系,所以即使checkscope函数执行上下文已经销毁,但是checkscopeContext.AO仍活在内存中,f函数仍能通过作用域链找到scope的值。

问题

for (var i = 0; i < 5 ; i++){
    setTimeout(function fn() {
        console.log(i)         
    }, 1000)
}

答案都是5,我们的预期是顺序输出0到4,和我们的预期不同。我们分析一下原因:

在执行定时器内fn函数之前,全局上下文的VO为:

globalContext = {
    VO: {
        i: 5
    }
}

当fn函数执行时,fn函数的作用域链为:

fnContext = {
    Scope: [AO, globalContext.VO]
}

在fnContext的AO中并没有i的值,所以会从globalContext.VO查找,而这时在globalContext.VO中i的值为5,所以最终打印出来的结果为5.

很多人会使用下面这种方法:

for (var i = 0; i < 5 ; i++){
    setTimeout((function fn(i) {
        console.log(i)         
    })(i), 1000)
}

我们发现输出的结果符合我们的预期,但是输出结果时并没有1s的延时。

这是因为在定时器内的第一个()是一个分组操作符,里面的函数fn为一个函数表达式而非函数声明,所以定时器内第一个参数相当于执行fn(i),并不会延时执行,这也就是我们常说的立即执行函数。

接下来我们继续使用闭包来解决这个问题:

for (var i = 0 ; i < 5 ; i++){
    setTimeout((function(i) {
        return function fn() {
            console.log(i)         
        }
    })(i), 1000)
}

我们在fn函数外层加一个立即执行函数并将i的值传入,这样就能够得到我们想要的答案,我们对比之前的fn函数执行时的作用域链:

fnContext = {
    Scope: [AO, 匿名Context.AO, globalContext.VO]
}

在闭包中fn函数的作用域链中多了一个匿名函数的AO,在这个AO中便存有匿名函数执行时保存在AO中的i值。这样在匿名函数的AO中找到i的值,也就不会再去找globalContext.VO中i值。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值