第3期 垃圾回收机制和内存泄漏

109 阅读2分钟

在JS中,我们创建变量、函数等都会消耗内存,如果不及时回收一些不会需要的内存,那么就会造成内存泄漏。所以我们需要对不再需要的内存进行回收,即垃圾回收。

但是如何定义垃圾,就造成不同的垃圾回收机制。

JS有自己的垃圾回收机制。以下介绍两种。

引用计数(现代浏览器不再使用)

是否被引用

var o = { 
  a: {
    b:2
  }
}; 

此时啊a b都被引用了,不会被回收。

但是这种算法无法处理循环引用,即创建两个对象,互相引用。即使执行上下文被pop,o和o2也不会被回收。

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o
  return "azerty";
}
f();

标记-清除算法

是否能获得

垃圾回收器从ROOT(在JS中是全局对象)开始查找,能够查找到的都添加标记,标记结束后,对所有内存进行查找,遇到没有标记的就进行回收,回收结束后,清空所有的标记,为下一轮标记-清除做准备。

这种算法也解决了引用计数中的循环引用的问题。

内存泄漏

一般来说,浏览器周期短,内存进行刷新操作或者关闭操作内存都会被释放,不容易导致内存泄漏。

但是在某些高频的操作中,不断的创建却又不进行回收就会导致内存泄漏。

或者是在nodejs搭建的网站,有些操作不当,导致内存随着时间慢慢增大而导致内存泄漏。

四种内存泄漏的场景

全局变量

function foo(arg) {
    bar = "this is a hidden global variable";
}

计时器

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

回调函数

var element = document.getElementById('button');
function onClick(event) {
    element.innerHTML = 'text';
}
element.addEventListener('click', onClick);

闭包

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);

DOM的引用

var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};

function removeButton() {
    document.body.removeChild(document.getElementById('button'));
    // 即使此处remove了button节点
    // 但是仍旧存在一个全局的 #button 的引用
    // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。
}