问题代码:
首先看下面一段代码:
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html>
- <head>
- <meta content="text/html;charset=utf-8" http-equiv="content-type" />
- <script src="jquery-1.3.2.js" type="text/javascript">
- </script>
- <style type="text/css">
- #inner {
- margin:0 auto;
- width:150px;
- height:50px;
- border:1px solid green;
- }
- </style>
- <script type="text/javascript">
- $(function(){
- $("#inner").click(function(){
- //错误,引起内存泄露
- $("#test")[0].innerHTML="";
- //正确做法
- //$("#test").empty();
- });
- });
- </script>
- <title>测试</title>
- </head>
- <body>
- <div style="height:500px;width:500px;border:1px solid red;padding-top:100px;" id="test">
- <div id="inner">click to remove me</div>
- </div>
- </body>
- </html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="content-type" />
<script src="jquery-1.3.2.js" type="text/javascript">
</script>
<style type="text/css">
#inner {
margin:0 auto;
width:150px;
height:50px;
border:1px solid green;
}
</style>
<script type="text/javascript">
$(function(){
$("#inner").click(function(){
//错误,引起内存泄露
$("#test")[0].innerHTML="";
//正确做法
//$("#test").empty();
});
});
</script>
<title>测试</title>
</head>
<body>
<div style="height:500px;width:500px;border:1px solid red;padding-top:100px;" id="test">
<div id="inner">click to remove me</div>
</div>
</body>
</html>
场景:
inner div 就是常见的我们可能使用 ajax 从服务器动态构造的元素,或者注入的片断html,效果为点击 inner 后就把它以及它的兄弟清除出去,依照传统的思路,直接 innerHTML =“” 就好了 ,可能你会想到所有删除元素的事件监听器应该清楚掉,但是对于除了 ie6 (引起内存泄露)以外的浏览器,清除事件监听器不是必要的 。
jquery 机制:
但是可以确定的是在 jquery 中用 innerHTML 的方法来清空元素,是必然会导致内存泄露的,由于 jquery 对于同一元素多事件处理没有直接采用浏览器事件模型,而是自己缓存事件,遍历触发,以及便于 trigger 程序触发 :
- // Init the element's event structure
- var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
- handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
- // Handle the second event of a trigger and when
- // an event is called after a page has unloaded
- return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
- jQuery.event.handle.apply(arguments.callee.elem, arguments) :
- undefined;
- });
采用 data 方法,将一些数据关联到了元素上面,上述事件即是采用该机制缓存事件监听器。
那么就可以知道,直接 innerHTML=“” 而不通知 jquery 清空与将要删除元素关联的数据,那么这部分数据就再也释放不了了,即为内存泄露。
解决:
jquery 已经想到了这一方面,提供了 empty 函数,其思想就是,对节点的所有子,孙,重孙.....节点( $("*",this) ),先清空它们关联的数据,再进行节点的删除:
- remove: function( selector ) {
- if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
- // Prevent memory leaks
- jQuery( "*", this ).add([this]).each(function(){
- jQuery.event.remove(this);
- jQuery.removeData(this);
- });
- if (this.parentNode)
- this.parentNode.removeChild( this );
- }
- },
- empty: function() {
- // Remove element nodes and prevent memory leaks
- jQuery(this).children().remove();
- // Remove any remaining nodes
- while ( this.firstChild )
- this.removeChild( this.firstChild );
- }
jquery导致IE6崩溃的继续研究
经过一系列的测试,我认为,jquery可能存在较严重的,导致浏览器崩溃的BUG
事件经过
1. 我在一个项目中引用了jquery,多数都是用于ajax请求的,在IE6会导致浏览器崩溃(IE7不会,经过10台以上计算机的测试,都存在,不是个别现象),初步认为是jquery的ajax并发导致的
2. 使用我以前封装的ajax类(也是应用于项目过的),同样会有这个问题,排除ajax问题。
3. 在网上发现,js内存溢出和窗口句柄的占用可能导致IE崩溃,于是重写方法,使用对象化,并在最后清除内存,还是会崩溃。
4. 最后,不使用任何jquery方法,采用原始的javascript,没有再崩溃了。。。。
使用的是jquery 1.1.2 pack版,已验证,源码版和pack版都有这样的问题,尚未验证1.1.1版有没有这个问题。
附件是测试的demo,经过简化处理,数据是读取的xml。
calendar_OOP_Jquery.htm 采用对象化的js,使用了jquery。不出10次就会崩溃
calendar_noOOP_Jquery.htm 使用jquery。不出10次就会崩溃
calendar_noOOP_noJquery.htm 没有采用面向对象js,没有使用jquery。测试到300次没有崩溃
日历算法的代码可以无偿使用、传播和修改,如果发现有什么BUG,请和我联系cimmicola#163.com
我实在是无语了。仅仅是使用了$选择器和html()方法。
autoOpen: false ,
width: 600 ,
buttons: {
" Ok " : function() {
$( this ).dialog( " close " );
},
" Cancel " : function() {
$( this ).dialog( " close " );
}
}
});
在一些JS交互性不多的一般页面来说,没有任何问题!但是对于交互性强的,需要动态加载与释放DOM的页面来说,它就是一个悲剧的东西!为什么这样说?大家看下下面的例子:
一段简单的代码,一个DIV是通过动态加载到页面上,然后对该DIV用Dialog进行绑定,以达到弹出的目的!下面的test元素就是<div id="test"></div>。
$( " #test " ).append( ' <div id="dialog"><div id="fileQueue"></div> <input type="file" name="uploadify" id="uploadify" /> ' +
' <a href="javascript:upload();">上传</a> ' +
' <a href="javascript:$(#uploadify).uploadifyClearQueue()">取消上传</a><div> ' );
$( ' #dialog ' ).dialog({
autoOpen: false ,
width: 600 ,
buttons: {
" Ok " : function() {
$( this ).dialog( " close " );
},
" Cancel " : function() {
$( this ).dialog( " close " );
}
}
});
return false ;
}
接着,我需要删除该DOM元素,一般来说,正常的做法都是$("#test").empty();这行简单的代码就完成了!这样有效吗?!当执行完这样代码后,你再用$('#dialog')来获取dialog元素,郁闷的事情发生了,既然获取到了!为什么!不是已经empty了吗!
下面我们来看下这一悲剧是如何造成的,
我们把注意点放到$('#dialog').dialog上面,然后看看JQuery的实现代码是如何写的,当我们跟踪代码到dialog类中的_create方法的时候,问题的原因找到了,看下面这段代码:
.appendTo(document.body)
.hide()
.addClass(uiDialogClasses + options.dialogClass)
.css({
zIndex: options.zIndex
})
// setting tabIndex makes the div focusable
// setting outline to 0 prevents a border on focus in Mozilla
.attr( ' tabIndex ' , - 1 ).css( ' outline ' , 0 ).keydown(function( event ) {
if (options.closeOnEscape && event .keyCode &&
event .keyCode === $.ui.keyCode.ESCAPE) {
self.close( event );
event .preventDefault();
}
})
.attr({
role: ' dialog ' ,
' aria-labelledby ' : titleId
})
.mousedown(function( event ) {
self.moveToTop( false , event );
}),
它既然也动态创建一个div,而且把该div加到了Body上面,然后把dialog中的元素从<div id=test>中移除,加入到该新的div中.....
这就是为什么我们$("#test").empty()后,却对内部的dialog没有起作用了!而且这有一个最不好的一个地方,也是最容易出现内存泄露的地方:它动态的在Body中创建了一个div,这样如果窗体不关闭的话,而你又在不察觉的情况下不断的使用上面的TestAppend方法来动态加载DOM,就会创建N个这样的div!
其实这个问题郁闷的地方不是在如何解决,而且隐藏的很深,很难发现!那么发现之后解决起来就变的简单多了:
$( ' #dialog ' ).parent().empty();
$( ' #dialog ' ).parent().remove();
}
当前加上这段后代码后,再做$("#dialog")来测试下,期望的结果终于出现了!dialog元素消失了!
- xhr:function()
- {
- return window.activexobject ? new activexobject("microsoft.xmlhttp") : new xmlhttprequest();
- }
xhr:function()
{
return window.activexobject ? new activexobject("microsoft.xmlhttp") : new xmlhttprequest();
}
直接崩溃掉,从上篇文章中了解到,microsoft.xmlhttp为ie下最早的一个xmlhttp版本,看来jquery的开发者也有意的将ie6系列的浏览器抛弃
ie7 已经开始支持xmlhttprequest.
最后将这段代码改写成如下模样
- var orequest;
- if(typeof xmlhttprequest=="undefined" && window.activexobject)
- {
- var arrsignatures = ["msxml2.xmlhttp.5.0","msxml2.xmlhttp.4.0","msxml2.xmlhttp.3.0","msxml2.xmlhttp","microsoft.xmlhttp"];
- for(var i=0;i<arrsignatures.length;i++)
- {
- try
- {
- orequest = new activexobject(arrsignatures[i]);
- return orequest;
- }
- catch(oerror)
- {
- }
- }
- }
- else
- orequest=new xmlhttprequest();
- return orequest