ChuChencheng / note

菜鸡零碎知识笔记
Creative Commons Zero v1.0 Universal
3 stars 0 forks source link

JavaScript 垃圾回收与内存泄漏 #15

Open ChuChencheng opened 4 years ago

ChuChencheng commented 4 years ago

垃圾回收

引用计数

判断对象是否被其他对象引用,如果没有,则可回收(零引用)

缺陷:无法处理循环引用导致内存泄漏

例如:

function func () {
  var obj1 = {}
  var obj2 = {}
  obj1.a = obj2
  obj2.a = obj1
}

func()

上述示例中,即使没有再用到 obj1, obj2 ,但它们各自被引用了一次,因此不会被回收

标记清除

从根对象(全局对象)开始遍历所有可以获得的对象,如果对象无法获得,则可以回收

上述循环引用的示例中,在函数调用后,从根对象开始都无法再获得 obj1obj2 ,因此可以被回收

限制:要清除一个对象,需要手动将它变成无法获得,比如 obj = null 等。个人认为这不是个问题。

参考

MDN

引起内存泄漏的原因

全局变量

一般是无意中声明的,例如:

function func () {
  property = 1
}

还有一种情况是 this 的使用问题:

function func () {
  this.property = 1
}

// 直接在全局作用域调用
func()

上述 this 在非严格模式下指向的是全局对象,可以通过 use strict 避免这种情况的发生。

计时器或回调

一般是使用 setInterval 忘了清除导致的,不仅回调内的变量不会被清除,回调函数引用的外层作用域的变量也无法被正常回收:

let a = 1
setInterval(function () {
  let b = a
}, 1000)

还有一点是各类监听事件的回调,例如 addEventListeners 的回调,在不需要监听后,如果没有 remove ,在旧的 IE 下会导致泄漏,因为旧 IE 用的是引用计数算法,无法清除循环引用。

尽管现代浏览器可以识别循环引用,开发者不必再手动 remove 监听的回调,但动手清理一下还是个比较好的实践,特别是在编写库的时候,可以防止在旧浏览器上出现这类泄漏

离 DOM 引用

有时候我们将 DOM 的引用存入 JS 数据结构中进行一些操作。当要删除这个 DOM 节点时,记得把 JS 代码中的这个 DOM 引用也释放掉:

var $button = document.getElementById('button')

// ...do something

$button.remove()

// 记得清除引用
$button = null

闭包

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing)
      console.log('hi');
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log('someMessage');
    }
  };
};
setInterval(replaceThing, 1000);

上述代码中,

replaceThing 函数中的作用域同时被 unused 与 theThing.someMethod 所引用,由于 theThing 为全局变量,所以被 unused 与 theThing 引用的变量不会被释放,这样被 unused 引用的 originalThing 就没有被释放(这块即为泄露的内存)

网上讨论是浏览器引擎作用域设计问题,不是泄漏问题,这边不做讨论。

识别内存泄漏的方法

浏览器方面,使用 Chrome devtools 的 Performance monitor 面板,一段时间内多次点击回收垃圾按钮(Collect garbage),如果 JS heap size 曲线呈现上升趋势而不是趋于平稳,则多半发生了内存泄漏问题。

如何预防和解决内存泄漏问题

  1. 养成良好的编码习惯,减少全局变量的定义
  2. 计时器记得 clear ,监听的事件在不用时记得 remove
  3. 对 DOM 的引用要注意,没有使用后需要手动释放内存
  4. 关于闭包不做解释。

后话

对于闭包的问题,明天就 0202 年了,只要设计得当,对于闭包的利用价值是大于防止泄漏的,而且很大一部分泄漏问题是历史遗留问题,比如 IE 的垃圾回收 bug ,在今天大可不必刻意去避免闭包的使用。

参考