innerHTML引起IE的内存泄漏
Posted 有梦就能实现
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了innerHTML引起IE的内存泄漏相关的知识,希望对你有一定的参考价值。
内存泄漏常见的原因有三种:
1. 闭包
2. 未解除事件绑定
3. 循环引用DOM元素
除此之外,还有一种泄漏原因少有人知,它和innerhtml有关,不过很容易解决。
出现这种内存泄漏需要有三个条件:
1. 内存中存在一个未加入DOM树的元素
2. 给这个元素设置innerHTML,注意,必须是能创建元素并且绑定了DOM 0级事件
3. 必须在这个元素加入DOM树前设置它的innerHTML
举个例子:
// 创建一个仅存在于内存中的元素 var el = document.createElement( ‘div‘ ); // 设置innerHTML el.innerHTML = ‘<a onclick = "testFn()">Test Link</a>‘ ; // 加入DOM树 document.body.appendChild(el) |
这种写法很常见对吧,但你根本察觉不到有内存泄漏。唯一的隐患在于,当你在一个相同的页面上频繁地用这种方式设置innerHTML,一次又一次,反反复复,没完没了,好吧,其实也没那么多次,总之是很多次之后,就会出现问题了。
肯定有人会说,谁那么蛋疼地总折腾一个元素,其实在ajax泛滥的时代,经常需要动态更新页面,所以这种情况也并非罕见。
如果实在不信,这里有两个DEMO页面:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>IE innerHTML Memory Leak Demo</title> <style type = "text/css"> html,body { font-family: arial; font-size: 120%; } div a { font-size: 120%; display: block; margin: 5px; padding: 5px; border: 2px solid #000; background-color: lightgreen; } </style> <script type = "text/javascript"> var btnStart, btnStop; function init() { btnStart = document.getElementById(‘btnStart‘); btnStop = document.getElementById(‘btnStop‘); btnStart.onclick = startLeak; btnStop.onclick = stopLeak; } function startLeak() { btnStart.disabled = true; btnStop.disabled = false; leak(); } function stopLeak() { btnStop.disabled = true; btnStart.disabled = false; } function leak() { if (btnStop.disabled == true) { return; } var str = ‘‘; var i, len = 2000; for (i = 0; i < len; i++) { str += ‘<a onclick = "test()">Test Link</a>‘; } var elem = document.getElementById(‘testDiv‘); if (elem) document.body.removeChild(elem); var elem = document.createElement(‘div‘); elem.id = ‘testDiv‘; // Oops! Setting .innerHTML first, and _then_ calling .appendChild(..) is asking for a memory leak! elem.innerHTML = str; document.body.appendChild(elem); setTimeout(leak, 250); } function test() { alert(‘Click!‘); return false; } window.onload = init; </script> </head> <body> <h1>IE innerHTML Memory Leak Demo</h1> <p>Upon clicking the "Start Leak" button, a script will execute repeatedly which creates a new <div> element in memory, sets its innerHTML to a string of 2000 <a> tags with onclick events wired up (‘<a onclick = "test()">Test Link</a>‘), and then adds that <div> to the page.</p> <p>Letting this script run for about 60 seconds, and using Perfmon to monitor memory consumption, you should notice a significant increase in the amount of memory consumed. To see the same script logic that doesn‘t leak memory, view the <a href = "./noleak.html">No Leak Page</a>.</p> <button id = "btnStart">Start Leak</button> <button id = "btnStop" disabled = "disabled">Stop Leak</button> </body> </html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>IE innerHTML Memory Leak Demo (the fix)</title> <style type = "text/css"> html,body { font-family: arial; font-size: 120%; } div a { font-size: 120%; display: block; margin: 5px; padding: 5px; border: 2px solid #000; background-color: lightgreen; } </style> <script type = "text/javascript"> var btnStart, btnStop; function init() { btnStart = document.getElementById(‘btnStart‘); btnStop = document.getElementById(‘btnStop‘); btnStart.onclick = startLeak; btnStop.onclick = stopLeak; } function startLeak() { btnStart.disabled = true; btnStop.disabled = false; leak(); } function stopLeak() { btnStop.disabled = true; btnStart.disabled = false; } function leak() { if (btnStop.disabled == true) { return; } var str = ‘‘; var i, len = 2000; for (i = 0; i < len; i++) { str += ‘<a onclick = "test()">Test Link</a>‘; } var elem = document.getElementById(‘testDiv‘); if (elem) document.body.removeChild(elem); var elem = document.createElement(‘div‘); elem.id = ‘testDiv‘; // Add the element to the DOM first, and /then/ set .innerHTML to prevent memory from leaking. document.body.appendChild(elem); elem.innerHTML = str; setTimeout(leak, 250); } function test() { alert(‘Click!‘); return false; } window.onload = init; </script> </head> <body> <h1>IE innerHTML Memory Leak Demo (the fix)</h1> <p>Upon clicking the "Start Leak" button, a script will execute repeatedly which creates a new <div> element in memory and then adds that element to the page. Only <em>after</em> the element has been added to the page, do we set its .innerHTML to a string to 2000 <a> tags with onclick events wired up (‘<a onclick = "test()">Test Link</a>‘).</p> <p>Letting this script run for about 60 seconds, and using Perfmon to monitor memory consumption, you should notice that, unlike the <a href = "./leak.html">Leak Page</a>, memory consumption remains relatively constant.</p> <button id = "btnStart">Start Leak</button> <button id = "btnStop" disabled = "disabled">Stop Leak</button> </body> </html>
接着来看怎么解决它:
其实很简单,换个顺序,先把元素加入DOM树,再设置innerHTML。
当然你也可以完全放弃使用innerHTML,这样做好处多多,比如不会存在未解除事件绑定的情况,但貌似完全放弃innerHTML也不现实。。。
以上是关于innerHTML引起IE的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章
如何使用模块化代码片段中的LeakCanary检测内存泄漏?
Release编译模式下,事件是否会引起内存泄漏问题初步研究