一文看懂前端垃圾回收

Runningfyy

简介

javascript里面变量不再使用,就是垃圾,我们就应该把它清除掉,以免占用内存。
但是垃圾回收过程是一个近似且不完美的方案,因为某块内存是否还有用,属于“不可判定的”问题,意味着靠算法是解决不了的。
目前主流浏览器使用的是标记清除,在介绍标记清除前先介绍下为什么引用计数会被淘汰。

引用计数

引用计数最早由 Netscape Navigator 3.0 采用,但很快就遇到了严重的问题:循环引用。
所谓循环引用,就是对象 A 有一个指针指向对象 B,而对象 B 也引用了对象 A。

function problem() {
let objectA = new Object(); 
let objectB = new Object();
objectA.someOtherObject = objectB; 
objectB.anotherObject = objectA;
}

在这个例子中,objectA 和 objectB 通过各自的属性相互引用,意味着它们的引用数都是 2。在 标记清理策略下,这不是问题,因为在函数结束后,这两个对象都不在作用域中。而在引用计数策略下objectA 和 objectB 在函数结束后还会存在,因为它们的引用数永远不会变成 0。如果函数被多次调 用,则会导致大量内存永远不会被释放。为此,Netscape 在 4.0 版放弃了引用计数,转而采用标记清理。

标记清除

  • 1.垃圾回收程序运行的时候,会标记内存中存储的所有变量。
  • 2.然后它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。
  • 3.在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。

可达性

这里介绍下第二步骤的一个实现。
可达性就行可访问性,window(globle)能访问某个变量A(不管通过几层取值),那A就是可达的,那么A就有可能再未来会被用,A就不会被删除。
我们看一下下图就能明白,左边可达,右边不可达,右边会再某个阶段会v8引擎回收

相关概念

闭包

明白了垃圾回收的概念,理解闭包就简单多了。
闭包为啥不销毁,因为当前函数执行完的返回值(一般是个函数)被外层上下文中的某个变量引用了,所以闭包中的环境可达,所以不会被销毁。
但是也因为不会被销毁,所以我们应该注意一下内存泄漏,也就是如果闭包占用的内存特别多,又把它赋给全局变量。而且这个操作我们是不断的进行的,那个内存就会越占越多,也就是发生了内存泄漏。

weakmap

还有个相关的考点,就是weakmap为什么不会被gc(Garbage Collection,垃圾收集)
因为它对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用。
防止内存泄漏我们需要手动置null。
map的键是对象,这个引用当于永远可达,这样不好,会有内存泄漏。
为了避免我们忘记置Null,es6添加了weakmap这种数据结构,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用

性能优化

  • 通过 const 和 let 声明提升性能
    因为 const和 let 都以块(而非函数)为作用域,所以相比于使用 var,使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存。
  • 多解除引用(闭包)
    优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要,那么把它设置为 null,从而释放其引用。这也可以叫 作解除引用。这个建议最适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用,

    参考资料

    1.垃圾回收
    2.JavaScript 内存泄漏教程
    3.前端面试:谈谈 JS 垃圾回收机制
    4.MDN-内存管理
    5.javascript高级程序设计第4版本

阅读 211

前端小羊羊
爱健身,爱编码,爱生活,欢迎小妹妹来撩我
920 声望
278 粉丝
0 条评论
你知道吗?

920 声望
278 粉丝
宣传栏