JavaScript 内存泄漏相关知识



1、什么是内存泄漏?

不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。

2、垃圾回收机制

垃圾回收机制怎么知道,哪些内存不再需要呢?

最常使用的方法叫做"引用计数"(reference counting):语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。

3、内存泄漏的识别方法

怎样可以观察到内存泄漏呢?

经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。这就要求实时查看内存占用。

3.1 浏览器

Chrome 浏览器查看内存占用,用开发者工具控制面板中perfomance查看内存占用情况。

3.2谷歌浏览器shift+esc:调用任务

3.3 命令行

命令行可以使用 Node 提供的process.memoryUsage方法。


console.log(process.memoryUsage());
// { rss: 27709440,
//  heapTotal: 5685248,
//  heapUsed: 3449392,
//  external: 8772 }

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下。

rss(resident set size):所有内存占用,包括指令区和堆栈。heapTotal:"堆"占用的内存,包括用到的和没用到的。heapUsed:用到的堆的部分。external: V8 引擎内部的 C++ 对象占用的内存。

判断内存泄漏,以heapUsed字段为准。

四、WeakMap

前面说过,及时清除引用非常重要。但是,你不可能记得那么多,有时候一疏忽就忘了,所以才有那么多内存泄漏。

最好能有一种方法,在新建引用的时候就声明,哪些引用必须手动清除,哪些引用可以忽略不计,当其他引用消失以后,垃圾回收机制就可以释放内存。这样就能大大减轻程序员的负担,你只要清除主要引用就可以了。

ES6 考虑到了这一点,推出了两种新的数据结构:WeakSetWeakMap。它们对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用。

下面以 WeakMap 为例,看看它是怎么解决内存泄漏的。


const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

上面代码中,先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。

也就是说,DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。

几种常见的js内存泄露

1、意外的全局变量

JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 window 。

例如:

haorooms ="这是一个全局的haorooms"

实际上生成了一个全局的haorooms,虽然一个简单的字符串,无伤大雅,也泄露不了多少内存,但是我们在编程中尽量少的避免全局变量!

另外一种全局变量可能由this创建。例如:

function foo() {
    this.variable = "potential accidental global";
}
// Foo 调用自己,this 指向了全局对象(window)
foo();

2、没有及时清理的计时器或回调函数

本文刚刚开始的时候,我就说了setInterval用多了,会占用大量的内存。因此setInterval我们必须及时清理!可以用如下方式清理setInterval。

function b() {
    var a = setInterval(function() {
        console.log("Hello");
        clearInterval(a);
        b();                
    }, 50);
}
b();

或者用2个函数:

function init()
{
    window.ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
    console.log('Hello');
    clearInterval(window.ref);
    init();
}
init();​

或者我们用setTimeout

function time(f, time) {
    return function walk() {
     clearTimeout(aeta);
        var aeta =setTimeout(function () {
            f();
            walk(); 
        }, time);
    };
}

time(updateFormat, 1000)();

3、脱离 DOM 的引用

有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。

var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};
function doStuff() {
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
    // 更多逻辑
}
function removeButton() {
    // 按钮是 body 的后代元素
    document.body.removeChild(document.getElementById('button'));
    // 此时,仍旧存在一个全局的 #button 的引用
    // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。
}

4、闭包

闭包注意事项我之前提及过,请看文章 http://www.haorooms.com/post/qianduan_xnyhbc

5、echart不停调用导致内存泄露

不停的用setInterval调用echart,更新echart表格及地图数据,及时清理了setInterval,也会导致内存泄露!

解决办法:

首先及时清理:

    myChart.clear();
    myChart.setOption(option);

但是你会发现,作用不大,那么如何处理呢?

我是如下做的:

第一次处理用

    myChart.clear();
    myChart.setOption(option);

后面用setInterval的时候,我是如下写的:

                            mapCharts.setOption({
                                series: [{
                                        data: _this.convertData(mapdata)
                                    }, {
                                        data: _this.convertData(mapdata.sort(function (a, b) {
                                            return b.value - a.value;
                                        }).slice(1, 6)),
                                    }, {
                                        data: _this.convertData(mapdata.sort(function (a, b) {
                                            return b.value - a.value;
                                        }).slice(0, 1))
                                    }]
                            },{notMerge: false, lazyUpdate: false, silent:false});

仅仅重新设置了series里面的数据,不是全部setOption(option);这样就不会内存泄露了!

猜你喜欢

转载自blog.csdn.net/qq_39207948/article/details/80237980