IE内存泄漏的恶心问题及其原因

 

不管微软是否承认,IE系列的内存泄漏问题始终是一个严重的Bug,即使到今天的IE8,解决仍不完美(在网上看到有人测试过,IE8自己也是声称改善,而没有说解决)。
内存泄漏大都发生在JavaScript对象与DOM对象进行交互的过程中。
大部分转自微软的一篇文章,分析得还是很透彻。
一般情况下,打开任务管理器后看到随着操作的进行,IE内存狂飙的情况有以下几种,由简入繁。
1. 重复写入对象的属性。
点击按钮执行下面的代码,你会发现内存不停的上涨。
解决方案就是避免这种写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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>

2. 在带有脚本的情况下,先创建整棵DOM树,然后将其一次性插入到DOM树中。
特别说明的是tree,tree的深度大于1。
解决得方案是从根节点开始创建,创建一个,插入一个。
此种情况下的内容泄漏是有前提条件的,即document.createElement的内容是含有JS脚本的元素,如div onClick=foo(),如果单独创建并插入DOM树,如div是没问题的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<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>

3. 循环引用
内存泄漏的主要方式之一,简单地说就是JavaScript对象或其属性指向了DOM元素,而DOM元素的属性又引用了刚刚的JavaScript对象,这样就形成了一个环,愚蠢的IE不知道如何解开它,进而导致内存泄漏。
看看下面的示例就什么都明白了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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>

大多数内存泄漏发生的情况要比上面的示例隐蔽得多,问题解决的关键就在于要将DOM元素上的引用置为null。
4. 闭包问题
微软的开发人员曾经说能不用闭包就尽量不用闭包,这种说法无异于“为了不堵厕所,请尽量不上厕所”。
先简单介绍一些啥是闭包:个人理解就是在某些情况下,内部函数将其要用到的各个对象、变量、属性等统一封装到一起“抛”到全局环境中。
下面的示例演示了闭包如何带来了内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<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>

看到了吗?函数执行结束后,指向闭包的引用也就消失了,但这个时候闭包仍然存在,而且成了典型的“三不管”,你无法通过向detachEvent这样的方法去干掉闭包。
微软一哥们提供了一种解决方案,设定指向闭包的expando属性来完成对闭包的清除,下面是示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<html>
  <head>
    <script language="JScript">
 
    function AttachEvents(element)
    {
      // In order to remove this we need to put
      // it somewhere. Creates another ref
      element.expandoClick = ClickEventHandler;
 
      // This structure causes element to ref ClickEventHandler
      element.attachEvent("onclick", element.expandoClick);
 
      function ClickEventHandler()
      {
        // This closure refs element
      }
    }
 
    function SetupLeak()
    {
      // The leak happens all at once
      AttachEvents(document.getElementById("LeakedDiv"));
    }
 
    function BreakLeak()
    {
      document.getElementById("LeakedDiv").detachEvent("onclick",
        document.getElementById("LeakedDiv").expandoClick);
      document.getElementById("LeakedDiv").expandoClick = null;
    }
    </script>
  </head>
  <body onload="SetupLeak()" onunload="BreakLeak()">
    <div id="LeakedDiv"></div>
  </body>
</html>

闭包的内容大概就是这么多。
对于循环引用和闭包,可以采用事件注册的机制去完成手工清理(将来会详细说明),对于另外的两种情况主要是在于程序的编写,赖不到微软的开发人员。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值