涉及jQuery Ajax请求的内存泄漏

时间:2009-09-21 18:09:46

标签: javascript jquery ajax memory-leaks

我有一个在IE8和Firefox中泄漏内存的网页; Windows Process Explorer中显示的内存使用量随着时间的推移不断增长。

以下页面请求“unplanned.json”url,这是一个永远不会更改的静态文件(尽管我将Cache-control HTTP标头设置为no-cache以确保始终保持Ajax请求穿过去)。当它获得结果时,它清除HTML表,循环从服务器返回的json数组,并为数组中的每个条目动态地向HTML表添加一行。然后等待2秒钟并重复此过程。

以下是整个网页:

<html> <head>
    <title>Test Page</title>
    <script type="text/javascript"
     src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
</head> <body>
<script type="text/javascript">
    function kickoff() {
        $.getJSON("unplanned.json", resetTable);
    }
    function resetTable(rows) {
        $("#content tbody").empty();
        for(var i=0; i<rows.length; i++) {
            $("<tr>"
                + "<td>" + rows[i].mpe_name + "</td>"
                + "<td>" + rows[i].bin + "</td>"
                + "<td>" + rows[i].request_time + "</td>"
                + "<td>" + rows[i].filtered_delta + "</td>"
                + "<td>" + rows[i].failed_delta + "</td>"
            + "</tr>").appendTo("#content tbody");
        }
        setTimeout(kickoff, 2000);
    }
    $(kickoff);
</script>
<table id="content" border="1" style="width:100% ; text-align:center">
<thead><tr>
    <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th>
</tr></thead>
<tbody></tbody>
</table>
</body> </html>

如果它有帮助,这里是我发回的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的插件。为简单起见,我没有应用任何一般性能建议offered by Peter。这是现在不再有内存泄漏的页面:

<html>
<head>
    <title>Test Page</title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
<!--
    $.fn.removefromdom = function(s) {
        if (!this) return;

        var el = document.getElementById(this.attr("id"));

        if (!el) return;

        var bin = document.getElementById("IELeakGarbageBin");

        //before deleting el, recursively delete all of its children.
        while (el.childNodes.length > 0) {
            if (!bin) {
                bin = document.createElement("DIV");
                bin.id = "IELeakGarbageBin";
                document.body.appendChild(bin);
            }

            bin.appendChild(el.childNodes[el.childNodes.length - 1]);
            bin.innerHTML = "";
        }

        el.parentNode.removeChild(el);

        if (!bin) {
            bin = document.createElement("DIV");
            bin.id = "IELeakGarbageBin";
            document.body.appendChild(bin);
        }

        bin.appendChild(el);
        bin.innerHTML = "";
    };

    var resets = 0;
    function kickoff() {
        $.getJSON("unplanned.json", resetTable);
    }
    function resetTable(rows) {
        $("#content tbody").removefromdom();
        $("#content").append('<tbody id="id_field_required"></tbody>');
        for(var i=0; i<rows.length; i++) {
            $("#content tbody").append("<tr><td>" + rows[i].mpe_name + "</td>"
                + "<td>" + rows[i].bin + "</td>"
                + "<td>" + rows[i].request_time + "</td>"
                + "<td>" + rows[i].filtered_delta + "</td>"
                + "<td>" + rows[i].failed_delta + "</td></tr>");
        }
        resets++;
        $("#message").html("Content set this many times: " + resets);
        setTimeout(kickoff, 2000);
    }
    $(kickoff);
// -->
</script>
<div id="message" style="color:red"></div>
<table id="content" border="1" style="width:100% ; text-align:center">
<thead><tr>
    <th>MPE</th>
    <th>Bin</th>
    <th>When</th>
    <th>Filtered</th>
    <th>Failed</th>
</tr></thead>
<tbody id="id_field_required"></tbody>
</table>
</body>
</html>

进一步编辑:我会保持我的问题不变,但值得注意的是,这个内存泄漏与Ajax无关。事实上,以下代码内存泄漏只是相同,并且使用Toran的removefromdom jQuery插件就可以轻松解决:

function resetTable() {
    $("#content tbody").empty();
    for(var i=0; i<1000; i++) {
        $("#content tbody").append("<tr><td>" + "DBOSS-095" + "</td>"
            + "<td>" + 4 + "</td>"
            + "<td>" + "09/18/2009 11:51:06" + "</td>"
            + "<td>" + 1 + "</td>"
            + "<td>" + 1 + "</td></tr>");
    }
    setTimeout(resetTable, 2000);
}
$(resetTable);

3 个答案:

答案 0 :(得分:11)

我不确定为什么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 = $("<div id='IELeakGarbageBin'></div>");
        $("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中的问题,您可以在我今年早些时候写的post博客中了解更多相关内容,当时我遇到了同样的问题。

编辑我将上面的插件更新为95%免费的JavaScript,所以它使用的jQuery比以前的版本更多。你仍然会注意到我必须使用innerHTML因为jQuery函数html(“”); IE6 / 7/8的行为不一样

答案 1 :(得分:4)

我不确定泄漏,但您的resetTable()功能非常低效。首先尝试解决这些问题并查看最终结果。

  • 不要在循环中附加到DOM。如果必须执行DOM操作,则附加到文档片段,然后将该片段移动到DOM。
  • 但是innerHTML总是比DOM操作更快,所以如果可以,请使用它。
  • 将jQuery集存储到本地变量中 - 无需每次都重新运行选择器。
  • 还在局部变量中存储重复的引用。
  • 当迭代任何类型的集合时,也将长度存储在局部变量中。

新代码:

<html> <head>
    <title>Test Page</title>
    <script type="text/javascript"
     src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
</head> <body>
<script type="text/javascript">
$(function()
{
    var $tbody = $("#content tbody");

    function kickoff() {
        $.getJSON("test.php", resetTable);
    }

    function resetTable(rows)
    {
        var html = ''
          , i = 0
          , l = rows.length
          , row;
        for ( ; i < l; i++ )
        {
            row = rows[i];
            html += "<tr>"
                + "<td>" + row.mpe_name + "</td>"
                + "<td>" + row.bin + "</td>"
                + "<td>" + row.request_time + "</td>"
                + "<td>" + row.filtered_delta + "</td>"
                + "<td>" + row.failed_delta + "</td>"
            + "</tr>";
        }
        $tbody.html( html );
        setTimeout(kickoff, 2000);
    }

    kickoff();
});
</script>
<table id="content" border="1" style="width:100% ; text-align:center">
<thead>
    <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th>
</thead>
<tbody></tbody>
</table>
</body> </html>

参考文献:

答案 2 :(得分:0)

如果我错了,请纠正我,但SetTimeout(fn)是否阻止释放调用者的内存空间?这样在resetTable(rows)方法期间分配的所有变量/内存都将保持分配,直到循环结束?

如果是这种情况,将字符串构造和appendTo逻辑推送到另一个方法可能会好一些,因为这些对象在每次调用后都会被释放,并且只返回值的内存(在这种情况下是字符串标记或如果新方法没有,那么appendTo()会保留在内存中。

本质上:

首次启动开始

- &GT;调用resetTable()

- &GT; - &GT; SetTimeout再次启动呼叫

- &GT; - &GT; - &GT;再次调用resetTable()

- &GT; - &GT; - &GT; - &GT;继续直到无限

如果代码永远无法解析,那么树会继续增长。

基于此释放一些内存的另一种方法就像下面的代码:

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 += "<tr>"
                + "<td>" + row.mpe_name + "</td>"
                + "<td>" + row.bin + "</td>"
                + "<td>" + row.request_time + "</td>"
                + "<td>" + row.filtered_delta + "</td>"
                + "<td>" + row.failed_delta + "</td>"
                + "</tr>";      
    }

    $("#content tbody").html(rowMarkup);
}

这会将标记附加到你的tbody然后完成堆栈的那一部分。我很确定“行”的每次迭代仍将保留在内存中;但是,标记字符串等应该最终释放。

再次......自从我在这个低级别看待SetTimeout以来已经有一段时间了,所以我在这里完全错了。无论如何,这不会消除泄漏,只会降低生长速度。这取决于使用的JavaScript引擎的垃圾收集器如何处理SetTimeout循环,就像你在这里一样。