`

javascript内存管理

阅读更多

当一个系统不能正确的管理他的内存分配时,这个系统就存在内存泄漏 ,这对系统来说是一个 bug 内存泄漏 的现象可以有程序调用失败、执行减慢等。

微软的 Internet Explorer 就存在一系列的内存泄漏 ,其中最严重的就算是执行 Jscript 时产生的泄漏。当一个 DOM 对象包涵有一个 JavaScript 对象(例如一个事件处理函数)的引用,同时如果这个 JavaScript 对象又包涵该 DOM 对象,那么这个循环引用就形成了。这种结构本质上没有问题。此时,因为该 DOM 对象和这个事件处理函数并没有别的引用存在,那么垃圾回收器(一种自动的内存资源管理器)本应该把它们都回收点,并内存释放。 JavaScript 的垃圾回收器能够检测到这种循环引用,并不会对他产生困惑。但是不幸的是, IE DOM 的内存并不能被 Jscript 所管理。他有他自己的内存管理系统,然而这套系统并不知道循环引用,使得一切都变得混乱。这就导致了,当循环引用形成的时候,内存释放工作不能完成,也就是产生了内存泄漏 。长时间的内存泄漏就将产生内存的匮乏,使得浏览器因缺乏必要内存而崩溃?

我们可以来演示一下。在第一段程序 -question1 中,我们将动态创建以 10 个为一组共计 10000 个的 DOM 元素( <span> ),创建 10 个然后删除在创建 10 个,如此循环。你在运行这段程序时打开 Windows 任务管理器,就可以观察到页面运行时 PF( 虚拟内存 ) 使用率一直保持不变。 PF 使用率的变化可以视为内存分配是否无效的指标。

 

Question1

<html

>

    

<head

>

        

<title

>Queue Test 1</title

>

    

</head

>

    

<body

>

        

<script

>

            

/*global setTimeout */

            

(function (limit, delay) {

                

var queue = new Array(10);

                

var n = 0;

 

                

function makeSpan(n) {

                    

var s = document.createElement('span');

                    

document.body.appendChild(s);

                    

var t = document.createTextNode(' ' + n);

                    

s.appendChild(t);

                    

return s;

                

}

 

                

function process(n) {

                    

queue.push(makeSpan(n));

                    

var s = queue.shift();

                    

if (s) {

                        

s.parentNode.removeChild(s);

                    

}

                

}

 

                

function loop() {

                    

if (n < limit) {

                    

    

process(n);

                        

n += 1;

                        

setTimeout(loop, delay);

                    

}

                

}

 

                

loop();

            

})(10000, 10);

        

</script

>

    

</body

>

</



html



>





 

接下来我们运行第二段程序 queuetest2 。除了做与 queuetest1 相同的事情以外,它还未每个元素添加了一个点击事件响应函数。在 Mozila Opera 上,虚拟 PF 利用率和 queuetest1 是一样的,但是在 IE 上我们可以看见由于内存泄漏 而产生的每秒一兆的虚拟内存的稳定增量,通常这种泄露都不会被注意到。但是由于 Ajax 的日益流行,使得页面在浏览器的停留时间增长,使得问题变得常见了。

Question2

<html>

    <head><title>Queue Test 2</title>

    </head>

    <body>

        <script>

            /*global setTimeout */

            (function (limit, delay) {

                var queue = new Array(10);

                var n = 0;

 

                function makeSpan(n) {

                    var s = document.createElement('span');

                    document.body.appendChild(s);

                    var t = document.createTextNode(' ' + n);

                    s.appendChild(t);

                    s.onclick = function (e) {

                        s.style.backgroundColor = 'red';

                        alert(n);

                    };

                    return s;

                }

 

                function process(n) {

                    queue.push(makeSpan(n));

                    var s = queue.shift();

                     if (s) {

                        s.parentNode.removeChild(s);

                    }

                }

 

                function loop() {

                    if (n < limit) {

                        process(n);

                        n += 1;

                        setTimeout(loop, delay);

                    }

                }

 

                loop();

            })(10000, 10);

        </script>

    </body>

</html>

 

因为 IE 不能对循环引用进行回收,所以这个任务就落在了我们的肩上。如果我们明确的打破这个循环引用,那么 IE 就能够完成垃圾回收工作了。具微软的解释,引起内存泄漏 的原因是闭包,然而这个结论肯定是非常错误的,并且这使得微软给开发者的建议也成了错误的建议。那么通过 DOM 来打破循环引用更简单,因为实际上不可能通过 Jscript 来实现。

当我们处理完一个元素后,我们必须通过把它所有的事件处理函数制空来达到破坏循环引用的目的。我们所需要做的就是把每个事件的处理函数设为空就可以了。我们甚至可以清理函数来完成这一工作。

清理函数将保存一份 DOM 元素的引用。它将循环检测这个元素的所有属性。如果发现了时间处理函数,就把它值为空。这样就破坏了循环引用,使得内存可以被回收释放。它同样也会检测该元素的子元素,打破他们的循环引用。这个清理函数,只在 IE 中有效果,对于 Mozilla Opera 都无效。不管是用 removeChild() 或者是设置 innerHTML 属性的值,都应该在删除元素之前调用清理函数。

function purge(d) {



    

var a = d.attributes, i, l, n;





    

if (a) {





        

l = a.length;





        

for (i = 0; i < l; i += 1) {





            

n = a[i].name;





            

if (typeof d[n] === 'function') {





                

d[n] = null;





            

}





        

}





    

}





    

a = d.childNodes;





    

if (a) {





        

l = a.length;





        

for (i = 0; i < l; i += 1) {





            

purge(d.childNodes[i]);





      

  

}





    

}





}



那么我们现在来运新第3 个程序,queuetest3 ,在程序3 里,元素在被删除之前都调用了清理函数。

Question3

<html>

    <head><title>Queue Test 3</title>

    </head>

    <body>

        <p>

            Queue Test 3 adds an event handler to each span, and removes it when

finished. See <a href="http://www.crockford.com/javascript /memory/leak.html">http://www.crockford.com/javascript/memory/leak.html</a>

        </p>

        <script>

            /*global onunload, setTimeout */

            (function (limit, delay) {

                var queue = new Array(10);

                var n = 0;

 

                function makeSpan(n) {

                    var s = document.createElement('span');

                    document.body.appendChild(s);

                    var t = document.createTextNode(' ' + n);

                     s.appendChild(t);

                    s.onclick = function (e) {

                        s.style.backgroundColor = 'red';

                        alert(n);

                    };

                    return s;

                }

 

                function purge(d) {

                    var a = d.attributes, i, l, n;

                    if (a) {

                        l = a.length;

                        for (i = 0; i < l; i += 1) {

                            n = a[i].name;

                            if (typeof d[n] === 'function') {

                                d[n] = null;

                            }

                        }

                    }

                    a = d.childNodes;

                    if (a) {

                        l = a.length;

                        for (i = 0; i < l; i += 1) {

                            purge(d.childNodes[i]);

                        }

                    }

                }

 

                function process(n) {

                    queue.push(makeSpan(n));

                    var s = queue.shift();

                    if (s) {

                        purge(s);

                        s.parentNode.removeChild(s);

                    }

                }

 

                function loop() {

                     if (n < limit) {

                        process(n);

                        n += 1;

                        setTimeout(loop, delay);

                    }

                }

                onunload = function (e) {

                    purge(document.body);

                };

 

                loop();

            })(10000, 10);

        </script>

    </body>

</html>

 

更新:微软发布了该问题的补丁: 929874 。如果你有十足的信心确保你所有的用户都可以获得该更新,那么你将不再需要上面的清理函数。但不幸的是,这不可能如你所愿,所以可能清理工作在 IE6 被淘汰之前还是有必要的。

这就是 web 的天性,有清理不完的 bugs

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics