【高性能JavaScript】读书笔记 - 数据存取(二) - 05


【简介】闭包的概念与闭包所带来的性能问题。

1. 闭包、作用域和内存(Closures, Scope, and Memory)

1-1. 闭包(Closures)

首先我们来梳理一下闭包:

首先我们回顾一下前面提到的「局部作用域内的函数和变量不能被在全局作用域内调用,全局作用域中的函数和变量能在局部作用域内调用。」
那么我们有没有办法实现在全局作用域中,调用局部作用域中的变量的功能呢?答案是有的。闭包就很好的实现了这个功能。

我们来看一个计数器的演示:

function timer() {
    // 定义一个局部变量 counter,用于计数
    var counter = 0;

    // 定义一个 add() 方法,用于累加 counter
    function add() {
        return counter += 1;
    }
    // 函数返回的是 add() 方法的引用
    return add;
}

// 执行 timer() 方法,并将函数返回的 add 引用赋给全局变量 count
var count = timer();
// 此时我们只要执行 count 计数器,就能实现调用局部变量 counter
// 实际上,执行 count() 的过程是调用 add() 方法
console.log("执行计数器:"+count());

counter 是一个局部变量,我们不能直接在全局作用域中调用,但是 timer() 函数中的 add() 方法却可以访问这个变量,所以我们 timer() 函数返回 add() 方法的应用,并将他存在全局变量 count 中,此时 全局变量 count 指向的是 add() 方法的引用,而add() 方法的作用域链有 counter 变量,所以counter 变量不会随着 timer() 方法的执行结束而销毁,因为它处于可能被调用的状态,JS的垃圾回收机制对这类仍然可能会被调用的变量不会回收。我们通过执行 count() ,就能调用counter的局部变量。实际上,我们执行的是 add() 方法。

看到这里,会不会有些纳闷,为什么我们要把这个 count 变量放在函数中,而不是放在全局作用域呢?放在全局作用域中,我们就能很方便的调用 counter 计数器了,但是,这也意味着,任何函数任何操作都可以调用到这个变量,在脚本数量比较少的情况下,或许我们还能手工维护,但随着web工程的扩大,引入了许多插件,扩展了许多其他脚本,就难以确保这个全局变量不会被其他脚本调用,这样的话,我们的计数器就失去意义了。实际上,这种情况我们称之为全局变量污染,对于构建可维护的脚本而言,是需要极力避免的。

说到这儿,我们来总结一下闭包,闭包帮我们解决了在全局作用域中,能够调用局部变量的问题。它的好处在于有利于我们编写可维护的脚本代码。

现在我们再来闭包的运行过程:
这里写图片描述
图中四个过程,就是我们通过闭包访问局部变量的过程。

最后我们再结合前面的作用域、作用域链、执行环境、活动对象来看看其内部实现机制。
不过首先我们要区分清楚两个概念:函数的定义和执行。

function timer() {
    var counter = 0;
    function add() {
        return counter += 1;
    }
    return add;
}
  • 执行:函数的调用过程。
timer();
  1. 在 timer 函数定义的时候,timer 的作用域链中为 window 对象。
  2. 在执行 timer 函数的时候,timer 会进入执行环境,此时会在 timer 的作用域链中动态地添加一个活动对象,即此时 timer 的作用域链中有两个元素:timer 的活动对象和 window 对象。
  3. 在执行 timer 的过程中,运行到 add 函数的定义的时候, add 的作用域链会有一个当前的执行环境的对象,即此时 add 的作用域链中有一个元素为 timer 对象,(注意 window对象也在 add 的作用域链中),此时 timer 执行结束。
  4. 在执行 add 函数的时候,同样会在 add 的作用域链中产生一个 add 的活动对象,所以,此时 add 的作用域链中有三个元素, add 的活动对象,timer 的活动对象,window 对象。所以,在 add 函数的执行过程中,按作用域链的顺序,依次在「add 的活动对象 -> timer 的活动对象 -> window 对象」中查找。因此,保存在 timer 活动对象中的局部变量没有随着 timer 函数的执行结束而被销毁,我们能在全局作用域中调用到这个局部变量。

    1-2. 闭包带来的性能问题

    在了解了闭包的运行机制后,我们就能深入地来讨论闭包所带来的性能问题了。通常来说,函数的活动的对象会随着函数的执行结束而销毁。但是,在引入闭包之后,本该被回收的 timer 的活动对象也被保留了下来,而这个活动对象就保存在了内存中。这意味着闭包的出现,增大了内存的开销。所以我们需要小心使用闭包,因为它关系着内存和执行速度。

参考博客
[1] JavaScript 闭包-菜鸟
[2] javascript深入理解js闭包
[3] JavaScript 闭包究竟是什么

附: 欢迎大家关注我的新浪微博 - 一点编程,了解最新动态 。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值