IE中的内存泄露

参考文章: Winter 的《浏览器中的内存泄露》

鸟食轩的《理解并解决IE内存泄露的方式[翻译]》

IBM的《JavaScript中的内存泄露模式》

还有两篇文章:

IE's memory-leak fix greatly exaggerated

Memory Leakage in Internet Explorer – revisited

IE中内存泄露的几种方式:

1、循环引用(Circular References) — IE浏览器的COM组件产生的对象实例和网页脚本引擎产生的对象实例相互引用,就会造成内存泄漏。这也是Web页面中我们遇到的最常见和主要的泄漏方式;

2、内部函数引用(Closures) — Closures可以看成是目前引起大量问题的循环应用的一种特殊形式。由于依赖指定的关键字和语法结构,Closures调用是比较容易被我们发现的;

3、页面交叉泄漏(Cross-Page Leaks) — 页面交叉泄漏其实是一种较小的泄漏,它通常在你浏览过程中,由于内部对象薄计引起。下面我们会讨论DOM插入顺序的问题,在那个示例中你会发现只需要改动少量的代码,我们就可以避免对象薄计对对象构建带来的影响;

4、貌似泄漏(Pseudo-Leaks) — 这个不是真正的意义上的泄漏,不过如果你不了解它,你可能会在你的可用内存资源变得越来越少的时候极度郁闷。为了演示这个问题,我们将通过重写Script元素中的内容来引发大量内存的"泄漏"。

循环引用:

<html>
    <head>
        <script language="JScript">

        var myGlobalObject;

        function SetupLeak()
        {
            // First set up the script scope to element reference
            myGlobalObject =
                document.getElementById("LeakedDiv");

            // Next set up the element to script scope reference
            document.getElementById("LeakedDiv").expandoProperty =
                myGlobalObject;
        }


        function BreakLeak()
        {
            document.getElementById("LeakedDiv").expandoProperty =
                null;
        }
        </script>
    </head>

    <body onload="SetupLeak()" onunload="BreakLeak()">
        <div id="LeakedDiv"></div>
    </body>
</html>

产生的原因:script engines handle circular references through their garbage collectors, but certain unknowns can prevent their heuristics from working properly.

内部函数引用:

<html>
    <head>
        <script language="JScript">

        function AttachEvents(element)
        {
            // This structure causes element to ref ClickEventHandler
            element.attachEvent("onclick", ClickEventHandler);

            function ClickEventHandler()
            {
                // This closure refs element
            }
        }

        function SetupLeak()
        {
            // The leak happens all at once
            AttachEvents(document.getElementById("LeakedDiv"));
        }

        function BreakLeak()
        {
        }
        </script>
    </head\>

    <body onload="SetupLeak()" onunload="BreakLeak()">
        <div id="LeakedDiv"></div>
    </body>
</html>

页面交叉泄露:

<html>
    <head>
        <script language="JScript">

        function LeakMemory()
        {
            var hostElement = document.getElementById("hostElement");

            // Do it a lot, look at Task Manager for memory response

            for(i = 0; i < 5000; i++)
            {
                var parentDiv =
                    document.createElement("<div onClick='foo()'>");
                var childDiv =
                    document.createElement("<div onClick='foo()'>");

                // This will leak a temporary object
                parentDiv.appendChild(childDiv);
                hostElement.appendChild(parentDiv);
                hostElement.removeChild(parentDiv);
                parentDiv.removeChild(childDiv);
                parentDiv = null;
                childDiv = null;
            }
            hostElement = null;
        }


        function CleanMemory()
        {
            var hostElement = document.getElementById("hostElement");

            // Do it a lot, look at Task Manager for memory response

            for(i = 0; i < 5000; i++)
            {
                var parentDiv =
                    document.createElement("<div onClick='foo()'>");
                var childDiv =
                    document.createElement("<div onClick='foo()'>");

                // Changing the order is important, this won't leak
                hostElement.appendChild(parentDiv);
                parentDiv.appendChild(childDiv);
                hostElement.removeChild(parentDiv);
                parentDiv.removeChild(childDiv);
                parentDiv = null;
                childDiv = null;
            }
            hostElement = null;
        }
        </script>
    </head>

    <body>
        <button onclick="LeakMemory()">Memory Leaking Insert</button>
        <button onclick="CleanMemory()">Clean Insert</button>
        <div id="hostElement"></div>
    </body>
</html>

而大多数情况下,并不会使用上面的这种方法去追加DOM节点(需要绑定事件的)

document.createElement("<div onClick='foo()'>");

通常是document.createElement,然后再使用绑定,但上面这个有事件在里面。保持自己的怀疑态度。

最后一种Pseudo-Leaks,也可以称之为伪泄露,而只有部分DOM元素会出现这种情况。

<html>
    <head>
        <script language="JScript">

        function LeakMemory()
        {
            // Do it a lot, look at Task Manager for memory response

            for(i = 0; i < 5000; i++)
            {
                hostElement.text = "function foo() { }";
            }
        }
        </script>
    </head>

    <body>
        <button onclick="LeakMemory()">Memory Leaking Insert</button>
        <script id="hostElement">function foo() { }</script>
    </body>
</html>

所以,我觉得上面的一些例子并不是十分符合实际开发中的一些写法和规范(如监听onclck事件的方法);只是如果你不小心在代码中写下与上面相似的代码,那么它就可能已经产生内存泄露了。

分析一些内存泄露的例子:

<html>
<head>
<script type="text/javascript">
    function LeakMemory(){
        var parentDiv = 
             document.createElement("<div onclick='foo()'>");

        parentDiv.bigString = new Array(1000).join(
                              new Array(2000).join("XXXXX"));
    }
</script>
</head>
<body>
<input type="button" 
       value="Memory Leaking Insert" onclick="LeakMemory()" />
</body>
</html>

内存好像没发生什么变化?但确实发生内存泄露,为什么,因为有onclick='foo()'

何以证明?

<html>
<head>
<script type="text/javascript">
    function LeakMemory(){
        for(i = 0; i < 5000; i++){
            var parentDiv = 
               document.createElement("<div onClick='foo()'>");
        }
    }
</script>
</head>
<body>
<input type="button" 
       value="Memory Leaking Insert" onclick="LeakMemory()" />
</body>
</html>
<html>
<head>
<script type="text/javascript">
    function LeakMemory(){
        for(i = 0; i < 50000; i++){
            var parentDiv = 
            document.createElement("div");
        }
    }
</script>
</head>
<body>
<input type="button" 
       value="Memory Leaking Insert" onclick="LeakMemory()" />
</body>
</html>

比较上面的两段代码,会发现仅仅是第一段比第二段多了一个内联脚本对象(onclick=’foo()’),它没有被正确的释放。

下面的代码也会产生内存泄露的问题:

<html>
<head>
<script type="text/javascript">
    function LeakMemory(){
        var parentDiv = document.createElement("div");
                          parentDiv.onclick=function(){
            foo();
        };

        parentDiv.bigString = 
          new Array(1000).join(new Array(2000).join("XXXXX"));
    }
</script>
</head>
<body>
<input type="button" 
       value="Memory Leaking Insert" onclick="LeakMemory()" />
</body>
</html>

因为onclick后面的function () {}能对parentDiv进行引用

更多循环引用的例子,如下图:

<html>
<head>
<script type="text/javascript">
    var myGlobalObject;

    function SetupLeak(){
        //Here a reference created from the JS World 

        //to the DOM world.

        myGlobalObject=document.getElementById("LeakedDiv");

        //Here DOM refers back to JS World; 

        //hence a circular reference.

        //The memory will leak if not handled properly.

        document.getElementById("LeakedDiv").expandoProperty=
                                               myGlobalObject;
    }
</script>
</head>
<body onload="SetupLeak()">
<div id="LeakedDiv"></div>
</body>
</html>
<html>
<head>
<script type="text/javascript">
window.onload=function(){
    // obj will be gc'ed as soon as 

    // it goes out of scope therefore no leak.

    var obj = document.getElementById("element");
    
    // this creates a closure over "element"

    // and will leak if not handled properly.

    obj.onclick=function(evt){
        ... logic ...
    };
};
</script>
</head>
<body>
<div id="element"></div>
</body>
</html>

改为下面的写法就不会产生内存泄露了

<html>
<head>
<script type="text/javascript">
window.onload=function(){
    // obj will be gc'ed as soon as 

    // it goes out of scope therefore no leak.

    var obj = document.getElementById("element");
    obj.onclick=element_click;
};

//HTML DOM object "element" refers to this function

//externally

function element_click(evt){
    ... logic ...
}
</script>
</head>
<body>
<div id="element"></div>
</body>
</html>

虽然IE有这么多的问题,但还是有工具可以检测你写的代码是否存在内存泄露,对于代码量少、复杂度并不高的可以使用sIEve,大项目中使用它想跟踪产生内存泄露的代码则比较困难了。好在还有一个工具:Javascript Leaks Detector

JLD的强大之处在于能够模拟IE6和IE7的GC情况,和真实的回收情况。这样可以做一个比较。

msdn上官方Blog地址:blogs.msdn.com/b/gpde/archive/2009/08/03/javascript-memory-leak-detector-v2.aspx

下载地址:http://joinmicrosofteurope.com/files/IEJSLeaksDetector2.0.1.1.zip

关于 Javascript Closures 可以点击这里查看

jibbering.com/faq/notes/closures/ (英文原文)

www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html 中文译文

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏coder修行路

Go基础--goroutine和channel

goroutine 在go语言中,每一个并发的执行单元叫做一个goroutine 这里说到并发,所以先解释一下并发和并行的概念: 并发:逻辑上具备同时处理多个任...

4185
来自专栏静晴轩

你所不知道的setTimeout

JavaScript提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成。它们向任务队列...

46312
来自专栏coding for love

JS常用设计模式解析01-单例模式

考虑实现如下功能,点击一个按钮后出现一个遮罩层。 原始办法:我们只需要实现一个创建遮罩层的函数并将其作为按钮点击的回调事件即可。如下:

1392
来自专栏从零开始学自动化测试

Selenium+python自动化82-只截某个元素的图

前言 selenium截取全图小伙伴们都知道,曾经去面试的时候,面试官问:如何截图某个元素的图?不要全部的,只要某个元素。。。小编一下子傻眼了, 苦心人,天不负...

4914
来自专栏梦魇小栈

面试分享:2018阿里巴巴前端面试总结(题目+答案)

最开始的思路是用定时器实现,最后没有想的太完整,面试官给出的答案是用requestAnimationFrame。

1323
来自专栏柠檬先生

sass 基础——回顾

1.webstorm 自动编译SASS   下载安装包 http://rubyinstaller.org/downloads/   然后点击安装,路径为默认路...

2357
来自专栏前端儿

深入理解JavaScript的事件循环(Event Loop)

在两个环境下的Event Loop实现是不一样的,在浏览器中基于 规范 来实现,不同浏览器可能有小小区别。在Node中基于 libuv 这个库来实现

1292
来自专栏互联网杂技

jQuery插件开发全解析

jQuery插件的开发包括两种: 一种是类级别的插件开发,即给jQuery添加新的全局函数,相当于给jQuery类本身添加方法。jQuery的全局函数就是属于j...

3757
来自专栏GreenLeaves

JS框架设计之加载器所在路径的探知一模块加载系统

1、要加载一个模块,我们需要一个URL作为加载地址,一个script作为加载媒介,但用户在require是都用ID,我们需要一个将ID转换为URL的方法,思路很...

2135
来自专栏听雨堂

.Net中使用带UI的OCX的方法

方法一:在工具箱中插入COM控件,当把控件拖到界面上后,将会自动产生两个封装的dll,并在引用中添加。 问题:当ocx需要不断升级时,这种方法很痛苦,需要重新...

1927

扫码关注云+社区

领取腾讯云代金券