jQuery在UpdatePanel中的内存泄漏

介绍

本文讨论如何把jQuery和Microsoft Ajax.net整合,特别是<asp:UpdatePanel />,并且在这样做时避免内存泄漏。

jQuery的应用是越来越流行了。然而Microsoft Ajax.net和jQuery提供了各自的和服务器通信的框架。jQuery机制在Asp.net环境中好象很笨拙和不自然。使用Asp.net的UpdatePanel实现Ajax功能和使用jQuery实现选择器是如此的容易,我理解的微软要做的是如此的漂亮。这样做是很不好处理的的,本文至少解释那样做的一些不足。

Iteration 1 - 示例页面

让我们假设有一个计算-把两个数字加到一起的页面例子。

<body> 
<form id="form1" runat="server">
<asp:ScriptManager runat="server" ScriptMode=Debug>
<Scripts>
<asp:ScriptReference Path="~/jquery-1.2.6.debug.js" />
</Scripts>
</asp:ScriptManager>
<asp:UpdatePanel ID='up' runat="server"><ContentTemplate>
<asp:TextBox ID='i1' runat="server" CssClass='num' Width='50px'/>+
<asp:TextBox ID='i2' runat="server" CssClass='num' Width='50px'/>=
<asp:TextBox ID='res' runat="server" Width='50px'/>
<asp:Button ID='btn' runat="server" Text='...'/>
</ContentTemplate></asp:UpdatePanel>
</form>
</body>
<script>
function add() {
$get('res').value = parseInt($get('i1').value)
+ parseInt($get('i2').value);
}
$(document).ready(function() {
$('.num').change(add);
});
</script>

 如果你对Asp.net和jQuery有所了解的话,这个例子是不需要做更多的解释的:

  • 我们包括了ScriptManager和jQuery脚本。
  • 标准form。
  • 我们定义了三个输入控件,前两个有class='num'属性,所以我们可以很容易在jQuery中找到它们。
  • 内容放置在<asp:UpdatePanel>中,并用按钮刷新这个面板。
  • 函数定义为取得两个数字,并把它们的加起来的结果放到第三个输入框中。
  • 标准的jQuery机制提供附加的事件(attach change event)使任意一个数字改变时,执行计算。

 结果页面是象这样的:


 最初看起来是正常工作的。你可以改变数字然后查看屏幕上的结果,然后刷新UpdatePanel,突然计算中止了。

说明

 UpdatePanel刷新后失败的原因是UpdatePanel中的内容所附加上的事件都丢失了。

为了解决这个问题,我们需要使用Ajax.net提供的附加到页面的事件的方法。(这里我只附加了脚本的一部分):

function add() {
$get('res').value =
parseInt($get('i1').value) + parseInt($get('i2').value);
}
Sys.Application.add_load(function() {
$('.num').change(add);
});

Iteration 2 - 内存泄漏

 我在Iteration 1中的方法暂时可以工作了。除此之外,我开始注意到了IE变得越来越慢。之后我查看进程监视器,我注意到每次UpdatePanel刷新会有1M左右的内存泄漏。我看到输入元素没有被销毁:

 

 你可以看到,每次我点击刷新按钮,它都创建了新的input元素但没有销毁原来的元素。

说明

google了一会,我发现IE内存泄漏是一个令很多开发者头疼的课题。大多投诉者指出的是假的内存泄漏(内存已经释放,但你需要刷新页面才能看到)或作为终结器(or as closures)。

终结器是一个有趣的题目,它指向了附加到DOM元素上的javascript事件。很显然,IE使用了两个垃圾回收器-一个针对DOM,另一个针对JavaScript。这有一些语义学上的混乱,但是如果你是最后的行(buttom line)上附加一个事件到DOM元素上,然后就不会有垃圾回收器能够移除调用的object/DOM。我强烈建议你在Web上阅读更多的关于终结器的内容。

jQuery关于终结器所做的是适当的。它很好的附加到了window.unload事件上,并在页面卸载时执行清理工作,但是它不能识别到UpdatePanel。所以当UpdatePanel刷新时需要运行一些对jQuery的清理工作。

Iteration 3 - jQuery 清理插件

 UpdatePanel不提供清理的钩子,当它要丢弃一个元素时,它检查该元素是否有element.dispose函数,如果有这样的一个函数,它将被执行。所以最后我写了下面一个jQuery插件来处理这种情况:

(function($) {
$.fn.Disposable = function(cln) {
return this.each(function() {
var el = this;
if (!el.dispose) {
el.dispose = cleanup; // will be called by
// Microsoft for cleanup
$(window).bind("unload", cleanup);
}
function cleanup() {
if (!el)
return;
$(el).unbind();
$(window).unbind("unload", cleanup);
el.dispose = null;
el = null;
};
});
};
})(jQuery);
function add() {
$get('res').value =
parseInt($get('i1').value) + parseInt($get('i2').value);
}
Sys.Application.add_load(function() {
$('.num').change(add).Disposable();
});

 如你所见,这个jQuery插件附加了一个cleanup()函数到element.dispose上。无论什么时候调用element.dispose,所有的jQuery事件将从该元素上解除,并允许垃圾回收器收回所有对象。

摘要

这里讨论的代码是前一段时间帖的基于Asp.net的jQuery库的一部分。欢迎各位下载项目并在闲暇时浏览代码。

授权

本文和相关的代码和文件,遵守The Code Project Open License (CPOL)授权协议。

关于作者

gstolarov                           Location: United States United States

 原文:http://www.codeproject.com/KB/ajax/jqmemleak.aspx

posted on 2009-04-08 16:40  常绍新  阅读(2318)  评论(1编辑  收藏  举报