关于JavaScript的内存泄漏的思考

1. 概念

  1) 内存泄漏就是指程序中不再用到的对象依然占用的内存无法释放;

  2) 程序中的内存过程:系统分配------程序使用 ------ 程序、系统释放

说到内存泄漏不得不提到垃圾回收机制

2. 垃圾回收机制

目前垃圾回收机制有两种:

  1) 引用标记法  

    优势:简单

    劣势:对于循环引用的对象无法清除

  2) 标记清除法--解决了循环引用对象

 

3. 常见的内存泄漏

1)全局变量

  对于定义的全局变量,由于挂在window上,除非刷新浏览器,这个变量就永远不会被回收

2)未销毁的定时器和回调函数

var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); // 每 5 秒调用一次

  如果后续renderer元素被移除,此时定时器就没有任何作用,但是如果没有清楚定时器,定时器的内存就没有被回收,这样serverData也无法被回收

3)闭包

  在 JS 开发中, 我们会经常用到闭包, 一个内部函数, 有权访问包含其的外部函数中的变量. 下面这种情况下, 闭包也会造成内存泄露.

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing) // 对于 'originalThing'的引用
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log("message");
    }
  };
};
setInterval(replaceThing, 1000);

  这段代码,每次调用replaceThing时,theThing获得了包含一个巨大的数组和一个对于新闭包someMethod的对象,同时unused是一个引用了originalThing的闭包。

    一旦为同一父作用域中的闭包创建了闭包范围,就会共享作用域。

  这个范例的关键在于,闭包之间是共享作用域,尽管unused可能一直没有被调用,但是someMethod可能会被调用,这样就会导致无法对其进行回收

  修改上述问题:

var theThing = null; 
var replaceThing = function(){ 
  var originalThing = theThing; 
  //定义一个引用originalThing但不会
  //实际被调用的闭包。但是因为这个闭包存在,所以
  // originalThing将在词汇环境中用于在replaceThing中定义的所有//闭包,而不是在其中进行优化
  //。如果删除此功能,则无泄漏。
  var unused = function(){ 
    if(originalThing)
      console.log(“hi”); 
  }; 
  theThing = { 
    longStr:new Array(1000000).join('*'),
    //虽然这个
    //函数理论上可以访问originalThing ,但它显然不会使用它。但是因为
    // originalThing是词法环境的一部分,someMethod 
    //将保存对originalThing的引用,所以即使我们
    //正在用无效的方式替换theThing 
    //引用旧的值ofThing,旧值
    //永远不会被清理干净!
    someMethod:function(){} 
  }; 
  //如果在这里添加`originalThing = null`,则没有泄漏。
}; 
setInterval(replaceThing,1000);

4)DOM引用

  很多时候,我们对DOM的操作会把DOM的引用保存在一个数组或者Map中

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 元素, 仍然无法被内存回收. 
}

  上述案例中,即使我们对于image元素进行了移除,但是仍然有对image元素的引用,依然无法进行内存回收

  另外需要注意的一个点是, 对于一个 Dom 树的叶子节点的引用. 举个例子: 如果我们引用了一个表格中的 td 元素, 一旦在 Dom 中删除了整个表格, 我们直观的觉得内存回收应该回收除了被引用的 td 外的其他元素. 但是事实上, 这个 td 元素是整个表格的一个子元素, 并保留对于其父元素的引用. 这就会导致对于整个表格, 都无法进行内存回收. 所以我们要小心处理对于 Dom 元素的引用.

 

参考原文:

https://blog.sessionstack.com/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks-3f28b94cfbec

https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156

posted @ 2019-04-11 15:50  MakeCoder  阅读(338)  评论(0编辑  收藏  举报