涉及jQuery Ajax请求的内存泄漏
我有一个在IE8和Firefox中泄漏内存的网页; Windows Process Explorer中显示的内存使用量随着时间的推移不断增长。
以下页面请求“unplanned.json”url,这是一个永远不会更改的静态文件(尽管我将我的Cache-control
HTTP标头设置为no-cache
以确保Ajax请求始终通过)。 当它获得结果时,它清除HTML表,循环从服务器返回的json数组,并为数组中的每个条目动态地向HTML表添加一行。 然后等待2秒钟并重复此过程。
这是整个网页:
Test Page function kickoff() { $.getJSON("unplanned.json", resetTable); } function resetTable(rows) { $("#content tbody").empty(); for(var i=0; i<rows.length; i++) { $("" + "" + rows[i].mpe_name + " " + "" + rows[i].bin + " " + "" + rows[i].request_time + " " + "" + rows[i].filtered_delta + " " + "" + rows[i].failed_delta + " " + " ").appendTo("#content tbody"); } setTimeout(kickoff, 2000); } $(kickoff); MPE Bin When Filtered Failed
如果它有帮助,这里是我发回的json的一个例子(它是这个确切的数组,有数千个条目而不是一个):
[ { mpe_name: "DBOSS-995", request_time: "09/18/2009 11:51:06", bin: 4, filtered_delta: 1, failed_delta: 1 } ]
编辑:我已经接受了Toran非常有帮助的答案,但我觉得我应该发布一些额外的代码,因为他的removefromdom
jQuery插件有一些限制:
- 它只删除单个元素。 所以你不能给它一个类似`$(“#content tbody tr”)`的查询,并期望它删除你指定的所有元素。
- 使用它删除的任何元素都必须具有`id`属性。 因此,如果我想删除我的`tbody`,那么我必须将`id`分配给我的`tbody`标签,否则它会出错。
- 它会删除元素本身及其所有后代,因此如果您只是想清空该元素,那么之后您将不得不重新创建它(或者将插件修改为空而不是删除)。
所以这是我上面的页面修改为使用Toran的插件。 为简单起见,我没有应用Peter提供的任何一般性能建议。 这是现在不再有内存泄漏的页面:
Test Page MPE Bin When Filtered Failed
进一步编辑:我会保持我的问题不变,但值得注意的是,这个内存泄漏与Ajax无关。 事实上,以下代码内存泄漏只是相同,并且使用Toran的removefromdom
jQuery插件就可以轻松解决:
function resetTable() { $("#content tbody").empty(); for(var i=0; i<1000; i++) { $("#content tbody").append("" + "DBOSS-095" + " " + "" + 4 + " " + "" + "09/18/2009 11:51:06" + " " + "" + 1 + " " + "" + 1 + " "); } setTimeout(resetTable, 2000); } $(resetTable);
我不知道为什么firefox不满意这个但我可以从经验中说,在IE6 / 7/8中你必须设置innerHTML =“”; 在您要从DOM中删除的对象上。 (如果你动态创建了这个DOM元素)
$("#content tbody").empty();
可能不会释放这些动态生成的DOM元素。
而是尝试类似下面的东西(这是我写的一个jQuery插件来解决问题)。
jQuery.fn.removefromdom = function(s) { if (!this) return; var bin = $("#IELeakGarbageBin"); if (!bin.get(0)) { bin = $(""); $("body").append(bin); } $(this).children().each( function() { bin.append(this); document.getElementById("IELeakGarbageBin").innerHTML = ""; } ); this.remove(); bin.append(this); document.getElementById("IELeakGarbageBin").innerHTML = ""; };
您可以这样称呼: $("#content").removefromdom();
这里唯一的问题是,每次要构建表时都需要重新创建表。
此外,如果这确实解决了您在IE中的问题,您可以在我今年早些时候写的一篇博文中了解更多相关内容,当时我遇到了同样的问题。
编辑我上面的插件更新为95%免费的JavaScript现在使用更多的jQuery比以前的版本。 你仍然会注意到我必须使用innerHTML因为jQuery函数html(“”); IE6 / 7/8的行为不一样
我不确定泄漏,但你的resetTable()
函数非常低效。 尝试先解决这些问题并查看最终结果。
- 不要在循环中附加到DOM。 如果必须执行DOM操作,则附加到文档片段,然后将该片段移动到DOM。
- 但是无论如何,innerHTML比DOM操作更快,所以如果可以,请使用它。
- 将jQuery集存储到局部变量中 – 无需每次都重新运行选择器。
- 还将重复引用存储在局部变量中。
- 迭代任何排序的集合时,也将长度存储在局部变量中。
新代码:
Test Page MPE Bin When Filtered Failed
参考文献:
- jQuery性能规则
- 加快你的Javascript
如果我错了,请纠正我,但SetTimeout(fn)是否阻止释放调用者的内存空间? 这样在resetTable(rows)方法期间分配的所有变量/内存都将保持分配,直到循环结束?
如果是这种情况,将字符串构造和appendTo逻辑推送到另一个方法可能会好一点,因为这些对象在每次调用后都会被释放,并且只返回返回值的内存(在这种情况下是字符串标记,如果是新方法使appendTo())保留在内存中。
在本质上:
初始召唤开始
– >调用resetTable()
– > – > SetTimeout再次调用启动
– > – > – >再次调用resetTable()
– > – > – > – >继续直到无限
如果代码永远不会真正解决,那么树将继续增长。
基于此释放一些内存的另一种方法就像下面的代码:
function resetTable(rows) { appendRows(rows); setTimeout(kickoff, 2000); } function appendRows(rows) { var rowMarkup = ''; var length = rows.length var row; for (i = 0; i < length; i++) { row = rows[i]; rowMarkup += "" + "" + row.mpe_name + " " + "" + row.bin + " " + "" + row.request_time + " " + "" + row.filtered_delta + " " + "" + row.failed_delta + " " + " "; } $("#content tbody").html(rowMarkup); }
这会将标记附加到你的tbody然后完成堆栈的那一部分。 我很确定“行”的每次迭代仍将保留在内存中; 但是,标记字符串等应该最终释放。
再一次……我已经有一段时间了,因为我在这个低级别看待SetTimeout所以我在这里完全错了。 在任何情况下,这都不会消除泄漏,只会降低生长速度。 这取决于使用的JavaScript引擎的垃圾收集器如何处理SetTimeout循环,就像你在这里一样。