JS 内存回收

引用

垃圾回收算法主要依赖引用的概念,例如一个对象如果有另外一个对象的访问权限,这里就叫做一个对象引用另外一个对象,不论这里是显式还是隐式

引用计数垃圾收集

这个算最简单的回收算法,大致是某地对象当没有引用指向它的时候,也就是零指向,这个对象就会被垃圾回收机制回收

let arr = [1,2,3]
arr = null  // [1,2,3]没有被引用,会被自动回收

循环引用,低版本浏览器

下面的例子中,两个对象相互引用,即使函数被调用完成,这里也不会被回收

function f() {
  var o1 = {};
  var o2 = {};
  o1.p = o2; // o1 引用 o2
  o2.p = o1; // o2 引用 o1. 这里会形成一个循环引用
}
f();

标记-清除算法

这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”
这个算法假定一个全局对象,然后定期的垃圾回收机制从全局开始,找所有从全局开始引用的对象,然后找这些对象引用的对象。这样就能找到所有可以获得的对象和不能获得的对象。
现代浏览器都使用了这个回收机制

img

自动GC的问题

自动化的GC很方便,但我们不知道它会什么时候执行,并且如果在使用程序的过程中,我们使用的大量的内存,然后GC没有运行,这时,程序就会假死或者卡顿,然后我们就需要手动做一些操作来触发内存回收

内存泄漏

本质上来说,内存泄漏就是不被需要的内存,因为某种原因,没有被回收或者释放

常见的内存泄漏

1.全局变量

function foo(arg) {
    bar = "some text";  // 挂在到window上
    this.arr = [1,2,3];  // this指向window
}

js中的全局变量,只有当页面被关闭后才会销毁

2.未销毁的定时器或回调函数

var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000);

上面的代码中,如果后续renderer元素被移除了,然后我们没有清除定时器,这时定时器里面的函数和定时器本身都不会被回收

3.闭包

function do(){
    let thing = 'eat'
    return function(){
        console.log(thing)
    }
}

4.DOM引用

有些时候,当我们要对DOM元素进行操作的时候,会把DOM的引用放在一个数组或对象中

var elements = {
    image: document.getElementById('image')
};
function doStuff() {
    elements.image.src = 'http://example.com/image_name.png';
}
function removeImage() {
    document.body.removeChild(document.getElementById('image'));
    // 这个时候我们对于 #image 仍然有一个引用, Image 元素, 仍然无法被内存回收.
}

建议

谨慎使用DOM操作,主动删除没有业务意义的变量,合理使用性能监控工具,分析内存的使用状况