美文网首页IT修真院-前端
JS哪些操作会造成内存泄漏?

JS哪些操作会造成内存泄漏?

作者: cczhuc | 来源:发表于2017-06-09 20:54 被阅读0次

    1.背景介绍

    内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。在C++中,因为是手动管理内存,内存泄露是经常出现的事情。而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使用的情况下几乎不会发生内存泄露。浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露。

    2.知识剖析

    js的回收机制:垃圾回收机制—GC

    Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

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

    1、标记清除

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

    function test(){

            var a = 10 ; //被标记 ,进入环境

            var b = 20 ; //被标记 ,进入环境

    }

    test(); //执行完毕 之后a、b又被标离开环境,被回收。

    2、引用计数

    引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是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

    }

    3.常见问题

    JS哪些操作会造成内存泄漏?

    4.解决方案

    虽然JavaScript会自动垃圾收集,但是如果我们的代码写法不当,会让变量一直处于“进入环境”的状态,无法被回收。下面列一下内存泄露常见的几种情况。

    1、意外的全局变量引起的内存泄漏

    function leaks(){

            leak = 'xxxxxx';//leak成为一个全局变量,不会被回收

    }

    2、闭包引起的内存泄漏

    function bindEvent(){

            var obj=document.createElement("XXX");

            obj.onclick=function(){

                    //Even if it's a empty function

            }

    }

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

    //将事件处理函数定义在外部

    function bindEvent() {

            var obj=document.createElement("XXX");

            obj.onclick=onclickHandler;

    }

    function onclickHandler(){

           //do something

    }

    //在定义事件处理函数的外部函数中,删除对dom的引用

    function bindEvent() {

            var obj=document.createElement("XXX");

            obj.onclick=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'));

    }

    虽然我们用removeChild移除了button,但是还在elements对象里保存着#button的引用,换言之, DOM元素还在内存里面。

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

    var someResource = getData();

    setInterval(function() {

            var node = document.getElementById('Node');

                if(node) {

                      node.innerHTML = JSON.stringify(someResource));

                }

    }, 1000);

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

    5、子元素存在引用引起的内存泄漏

    黄色是指直接被js变量所引用,在内存里

    红色是指间接被js变量所引用,如上图,refB被refA间接引用,导致即使refB变量被清空,也是不会被回收的

    子元素refB由于parentNode的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除

    5.编码实战

    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有一个名为element的属性指向element对象;而变量element也有一个属性名为o回指myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。

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

    window.onload=function outerFunction(){

            var obj = document.getElementById("element");

            obj.onclick=function innerFunction(){};

    };

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

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

    myObject.element = null;

    element.o = null;

    window.onload=function outerFunction(){

            var obj = document.getElementById("element");

            obj.onclick=function innerFunction(){};

            obj=null;

    };

    将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。

    要注意的是,IE9+并不存在循环引用导致Dom内存泄露问题,可能是微软做了优化,或者Dom的回收方式已经改变

    7.参考文献

    参考一:javascript的垃圾回收机制与内存管理http://www.jb51.net/article/75292.htm

    参考二:js内存泄漏常见的四种情况http://www.tuicool.com/articles/7J3amu

    8.更多讨论

    如何分析JS内存使用

    Google Chrome浏览器提供了非常强大的JS调试工具,Memory视图

    profiles视图让你可以对JavaScript代码运行时的内存进行快照,并且可以比较这些内存快照。它还让你可以记录一段时间内的内存分配情况。在每一个结果视图中都可以展示不同类型的列表,但是对我们最有用的是summary列表和comparison列表。

    summary视图提供了不同类型的分配对象以及它们的合计大小:shallow size(一个特定类型的所有对象的总和)和retained size(shallow size加上保留此对象的其它对象的大小)。distance显示了对象到达GC根(校者注:最初引用的那块内存,具体内容可自行搜索该术语)的最短距离。

    comparison视图提供了同样的信息但是允许对比不同的快照。这对于找到泄露很有帮助。

    JS内存泄漏排查方法---http://www.2cto.com/kf/201402/281855.html

    问题:1、全局变量如何清除。

               2、垃圾回收的机制:是根据什么来决定是否清除的。

    PPT地址:https://ptteng.github.io/PPT/PPT/js-11-JS%20memory%20leak.html#/

    视频地址:https://v.qq.com/x/page/x0512k9nmxt.html

    今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~

    下期预告:如何使用gulp?

    ------------------------------------------------------------------------------------------------------------------------

    技能树.IT修真院

    “我们相信人人都可以成为一个工程师,现在开始,找个师兄,带你入门,掌控自己学习的节奏,学习的路上不再迷茫”。

    这里是技能树.IT修真院,成千上万的师兄在这里找到了自己的学习路线,学习透明化,成长可见化,师兄1对1免费指导。快来与我一起学习吧~

    我的邀请码:96194340,或者你可以直接点击此链接:http://www.jnshu.com/login/1/96194340

    相关文章

      网友评论

        本文标题:JS哪些操作会造成内存泄漏?

        本文链接:https://www.haomeiwen.com/subject/bfwfqxtx.html