js闭包以及垃圾回收机制

闭包

函数可以访问定义时的外层作用域时,就产生了闭包
闭包是在定义时产生的,与在哪调用无关
广义上讲,所有js函数都是闭包

        function bar(age) {
            function baz() {
                console.log(age)
            }
            return baz
        }
        var func = bar(18)
        func()

在这个例子中我们将baz的函数对象返回,并使用func来接收这个对象
调用func其实就是调用baz
bar调用完毕后,垃圾回收机制通常会将这段内存回收,但闭包的神奇之处在于他会阻止这件事发生
因为baz此时正在使用bar的作用域
baz始终保持着对bar的引用,这个引用就叫做闭包

内存泄漏

正如上面所写的,闭包会导致js的垃圾回收机制无法回收对应的内存
这虽然方便了获取外层作用域的变量,但同样也会造成内存泄漏
要想知道为什么会造成内存泄漏,就要知道js中的垃圾回收机制是怎么工作的

内存管理

无论是什么编程语言,在代码的执行过程中都需要对内存进行操作,如分配内存释放内存等等
这个功能被称之为内存管理
具体而言,内存具有以下几个生命周期

  1. 申请内存
  2. 使用内存
  3. 释放内存

不同的编程语言对于内存的管理是有区别的
大体可分为自动手动

  1. 手动管理内存
    最常见的就是c语言,通过mallocfree来动态申请内存和释放内存
  2. 自动管理内存
    javaJavaScript,他们会自动帮助我们申请内存释放内存

具体到JavaScript中,JavaScript会在我们定义数据时分配内存
对于原始数据JavaScript会直接在栈内存中分配内存
对于复杂数据JavaScript会在堆内存中开辟一块空间用于存放并返回一个指针给变量

内存结构
内存使用完毕后就需要对其进行释放
以便腾出更多的内存
这个步骤就叫做垃圾回收(Garbage Collection,简称GC

垃圾回收机制

对于那些不再需要的对象,我们称之为垃圾,需要对其进行回收以腾出更多的内存
具体而言,垃圾回收机制通过算法来实现

常见GC算法

以下列举了一些常见的GC算法

引用计数

每当有一个对象能指向自己时,自己的引用数便+1
引用数0时就销毁这个对象
然而当产生循环引用时这个算法就无法处理
循环引用即对象a引用对象b,对象b引用对象a

循环引用

标记清除

标记清除的核心就是可达性,如果无法达到清除
算法会设置一个根对象垃圾回收器会定期从根对象开始寻找所有引用到的对象
对于不可达对象,就会对其回收
这个算法可以很好解决循环引用问题

标记清除

V8引擎的优化

V8引擎中广泛采用的是标记可达算法
同时为了对其进行更好的优化,在算法具体实现上也结合了其他一些算法

  1. 标记整理
    标记清除相似,不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间避免内存碎片化
  2. 分代收集
    对象会被分成两组:“新的”和“旧的
    新的对象在很快的时间内完成工作就会被清理
    如果对象长期存活就会变得“老旧”,被检查的频次也会减少
  3. 增量收集
    如果有许多对象,要一次遍历完的话会消耗很多时间,占用很多性能,并且会在操作中带来很大延迟
    所以引擎会试图将一次遍历操作分成几个部分来完成,这样会有许多微小的延迟而不是一个大的延迟
  4. 闲时收集
    垃圾回收器只会在CPU空闲时尝试运行,以减少可能对代码执行的影响

关于闭包导致的内存泄漏

再次说回关于闭包所引起的内存泄漏问题
我们来看这样一段代码

        function bar(age) {
            function baz() {
                console.log(age)
            }
            return baz
        }
        var func = bar(18)
        func()

注意这里的baz函数形成了闭包
这是这段代码运行到最后一行的内存状态
内存泄漏
第7行代码运行完后,我们期望bar对应的AO回收掉,但GC根据可达性原则,发现能从根对象寻找到barAO对象,所以就不对其进行回收
关于AO,GO等等可以看我这一篇文章
js执行原理
即如下图
内存泄漏
我们能从GO中找到func对象,能从funcscopes中寻找到bar对象,那么GC就无法对bar对象进行回收
解决方法也很简单,在确认对象使用完毕,以后不会再使用时,手动释放不用的内存,即将func赋为null

        function bar(age) {
            function baz() {
                console.log(age)
            }
            return baz
        }
        var func = bar(18)
        func()
        func=null

内存泄漏解决方案
当从GO对象中无法寻找到baz对象时,那么GC就会回收这些内存
这一切的前提都是这些对象确认不必再使用时才进行的

浏览器对闭包的优化

当闭包中存在着没有被使用的属性时,浏览器会将其销毁以节省空间
例如以下代码

        function bar() {
            var name = "bar"
            var age = 18
            function baz() {
                console.log(age)
            }
            return baz
        }
        var func = bar()
        func()

我们可以看到当运行baz函数时,它的作用域链中并没有name属性
1
控制台中也获取不到name
2

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值