JS 哪些操作会造成内存泄露

简介: JS 哪些操作会造成内存泄露

1)意外的全局变量引起的内存泄露

function leak() {
    leak = "xxx"; //leak成为一个全局变量,不会被回收
}

2)闭包引起的内存泄露

function bindEvent() {
    var obj = document.createElement("XXX");
    obj.οnclick = function() {
        //Even if it's a empty function
    };
}

闭包可以维持函数内局部变量,使其得不到释放。 上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包。 解决之道,将事件处理函数定义在外部,解除闭包, 或者在定义事件处理函数的外部函数中,删除对 dom 的引用。

//将事件处理函数定义在外部
function onclickHandler() {
    //do something
}
 
function bindEvent() {
    var obj = document.createElement("XXX");
    obj.οnclick = onclickHandler;
}
 
//在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent() {
    var obj = document.createElement("XXX");
    obj.οnclick = function() {
        //Even if it's a empty function
    };
    obj = null;
}

3)没有清理的 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() {
    document.body.removeChild(document.getElementById('button'))
}

4)被遗忘的定时器或者回调

var someResouce = getData();
setInterval(function() {
    var node = document.getElementById("Node");
    if (node) {
        node.innerHTML = JSON.stringify(someResouce);
    }
}, 1000);

这样的代码很常见, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。

5)子元素存在引起的内存泄露

黄色是指直接被 js 变量所引用,在内存里,红色是指间接被 js 变量所引用,如上图,refB 被 refA 间接引用,导致即使 refB 变量被清空,也是不会被回收的子元素 refB 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除。

6)IE7/8 引用计数使用循环引用产生的问题

function fn() {
    var a = {};
    var b = {};
    a.pro = b;
    b.pro = a;
}
fn();

fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为 a 和 b 的引用次数不为 0,所以不会被垃圾回收器回收内存,如果 fn 函数被大量调用,就会造成内存泄漏。在 IE7 与 IE8 上,内存直线上升。 IE 中有一部分对象并不是原生 js 对象。例如,其内存泄漏 DOM 和 BOM 中的对象就是使用 C++以 COM 对象的形式实现的,而 COM 对象的垃圾回收机制采用的就是引用计数策略。因此,即使 IE 的 js 引擎采用标记清除策略来实现,但 js 访问的 COM 对象依然是基于引用计数策略的。换句话说,只要在 IE 中涉及 COM 对象,就会存在循环引用的问题。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;

上面的例子在一个 DOM 元素(element)与一个原生 js 对象(myObject)之间创建了循环引用。其中,变量 myObject 有一个名为 e 的属性指向 element 对象;而变量 element 也有一个属性名为 o 回指 myObject。由于存在这个循环引用,即使例子中的 DOM 从页面中移除,它也永远不会被回收。

看上面的例子,有人会觉得太弱了,谁会做这样无聊的事情,但是其实我们经常会这样做

window.οnlοad = function outerFunction() {
    var obj = document.getElementById("element"):
        obj.οnclick = function innerFunction() {};
};

这段代码看起来没什么问题,但是 obj 引用了 document.getElementById(“element”),而 document.getElementById(“element”)的 onclick 方法会引用外部环境中的变量,自然也包括 obj,是不是很隐蔽啊。

最简单的解决方式就是自己手工解除循环引用,比如刚才的函数可以这样

myObject.element = null;
element.o = null;
window.οnlοad = function outerFunction() {
    var obj = document.getElementById("element"):
        obj.οnclick = function innerFunction() {};
    obj = null;
};

将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。 要注意的是,IE9+并不存在循环引用导致 Dom 内存泄漏问题,可能是微软做了优化,或者 Dom 的回收方式已经改变

解析:

1、JS 的回收机制

JavaScript 垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统(GC)会按照固定的时间间隔, 周期性的执行。

到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数。引用计数不太常用,标记清除较为常用。

2、标记清除(mark and sweep)

js 中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

function test() {
    var a = 10; //被标记,进入环境
    var b = 20; //被标记,进入环境
}
test(); //执行完毕之后a、b又被标记离开环境,被回收

3、引用计数(reference counting)

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值(function object array)赋给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存。

function test() {
    var a = {}; //a的引用次数为0
    var b = a; //a的引用次数加1,为1
    var c = a; //a的引用次数加1,为2
    var b = {}; //a的引用次数减1,为1
}

4、如何分析内存的使用情况

Google Chrome 浏览器提供了非常强大的 JS 调试工具,Memory 视图 profiles 视图让你可以对 JavaScript 代码运行时的内存进行快照,并且可以比较这些内存快照。它还让你可以记录一段时间内的内存分配情况。在每一个结果视图中都可以展示不同类型的列表,但是对我们最有用的是 summary 列表和 comparison 列表。 summary 视图提供了不同类型的分配对象以及它们的合计大小:shallow size (一个特定类型的所有对象的总和)和 retained size (shallow size 加上保留此对象的其它对象的大小)。distance 显示了对象到达 GC 根(校者注:最初引用的那块内存,具体内容可自行搜索该术语)的最短距离。 comparison 视图提供了同样的信息但是允许对比不同的快照。这对于找到泄漏很有帮助。

5、怎样避免内存泄露

1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;

2)注意程序逻辑,避免“死循环”之类的 ;

3)避免创建过多的对象 原则:不用了的东西要及时归还。

相关文章
|
13天前
|
Web App开发 监控 JavaScript
监控和分析 JavaScript 内存使用情况
【10月更文挑战第30天】通过使用上述的浏览器开发者工具、性能分析工具和内存泄漏检测工具,可以有效地监控和分析JavaScript内存使用情况,及时发现和解决内存泄漏、过度内存消耗等问题,从而提高JavaScript应用程序的性能和稳定性。在实际开发中,可以根据具体的需求和场景选择合适的工具和方法来进行内存监控和分析。
|
13天前
|
JavaScript 前端开发 Java
避免 JavaScript 中的内存泄漏
【10月更文挑战第30天】避免JavaScript中的内存泄漏问题需要开发者对变量引用、事件监听器管理、DOM元素操作以及异步操作等方面有深入的理解和注意。通过遵循良好的编程实践和及时清理不再使用的资源,可以有效地减少内存泄漏的风险,提高JavaScript应用程序的性能和稳定性。
|
26天前
|
存储 JavaScript 前端开发
JS 中的内存管理
【10月更文挑战第17天】了解和掌握 JavaScript 中的内存管理是非常重要的。通过合理的内存分配、及时的垃圾回收以及避免内存泄漏等措施,可以确保代码的高效运行和稳定性。同时,不断关注内存管理的最新发展动态,以便更好地应对各种挑战。在实际开发中要时刻关注内存使用情况,以提升应用的性能和质量。
26 1
|
18天前
|
监控 JavaScript 前端开发
如何检测和解决 JavaScript 中内存泄漏问题
【10月更文挑战第25天】解决内存泄漏问题需要对代码有深入的理解和细致的排查。同时,不断优化和改进代码的结构和逻辑也是预防内存泄漏的重要措施。
34 6
|
18天前
|
JavaScript 前端开发 Java
JavaScript 中内存泄漏的几种常见情况
【10月更文挑战第25天】实际上还有许多其他的情况可能导致内存泄漏。为了避免内存泄漏,我们需要在开发过程中注意及时清理不再需要的资源,合理使用内存,并且定期检查内存使用情况,以确保程序的性能和稳定性
28 2
|
21天前
|
存储 JavaScript 前端开发
js 中有哪几种内存泄露的情况
JavaScript 中常见的内存泄漏情况包括:1) 全局变量未被释放;2) 意外的全局变量引用;3) 被遗忘的计时器或回调函数;4) 事件监听器未被移除;5) 子元素存在时删除父元素;6) 循环引用。
|
1月前
|
缓存 监控 JavaScript
|
1月前
|
存储 缓存 JavaScript
|
30天前
|
JavaScript 前端开发 算法
深入理解JavaScript的内存管理机制
【10月更文挑战第13天】深入理解JavaScript的内存管理机制
32 0
|
3月前
|
Web App开发 存储 监控
Node.js中的内存泄漏
【8月更文挑战第31天】Node.js中的内存泄漏
83 1