jQuery 插件中的 JavaScript 内存泄漏

发布于 2024-12-21 20:24:32 字数 2198 浏览 0 评论 0原文

我有一个具有以下结构的插件:

(function($) {
    $.fn.drawFieldsTable = function(options) {
        var settings = {
                url        : 'ajax/pathToFile.php'
        };

        if (options) {
            settings = $.extend(settings, options);
        }

        return this.each(function() {
            $this = $(this);
            $.ajax({
                url  : settings.url,
                type : 'GET',
                data: // some request data here
                success: function(result) {
                    drawFieldElements(result, $this);
                }
            });

            function drawFieldElements(fields, container)
            {
                var replacement = container.clone()
                                           .empty();
                $.each(fields, function(i) {
                    var field;
                    field = buildFieldItem(i, this);
                    replacement.append(field);
                });

                container.replaceWith(replacement);
            }

            function buildFieldItem(i, fieldDataObj)
            {
                var $field = $('<div></div>')
                        .addClass('field')
                        .attr('data-fieldname', i)
                        .attr('data-ord', fieldDataObj.ord);
                return $field;
            }
        });

    };

})(jQuery);

每次 buildFieldItem 调用时我都会遇到严重的内存泄漏。如果我不调用此函数或将其设为空函数或只是不在其范围内使用其参数 - 则不会发生泄漏。泄漏仅发生在 Internet Explorer (8) 中。在其他浏览器中一切正常。请求帮助。提前致谢。

更新

我更新了我的问题,以便更好地演示 buildFieldItem 的实际用途。 一旦 fieldDataObj 可能包含大约 100 个项目,拥有这 4 行只会导致 IE 中的喷泉,而不是泄漏。

更新2

我已经用创建一个新的div替换了clone()和empty(),并且我还缩小了buildFieldItem看起来像:

function buildFieldItem(i, fieldDataObj)
{
    var $field = $('<div></div>')
            .addClass('field');
    return $field;
}

仍然泄漏。唯一不泄露的模式是:

function buildFieldItem(i, fieldDataObj)
{
    var $field;
    return $field;
}

看起来完全是胡说八道。函数中的代码越少,整体泄漏就越小。 IE 是否无法删除对整个函数或类似内容的引用?

I have a plugin with the following structure:

(function($) {
    $.fn.drawFieldsTable = function(options) {
        var settings = {
                url        : 'ajax/pathToFile.php'
        };

        if (options) {
            settings = $.extend(settings, options);
        }

        return this.each(function() {
            $this = $(this);
            $.ajax({
                url  : settings.url,
                type : 'GET',
                data: // some request data here
                success: function(result) {
                    drawFieldElements(result, $this);
                }
            });

            function drawFieldElements(fields, container)
            {
                var replacement = container.clone()
                                           .empty();
                $.each(fields, function(i) {
                    var field;
                    field = buildFieldItem(i, this);
                    replacement.append(field);
                });

                container.replaceWith(replacement);
            }

            function buildFieldItem(i, fieldDataObj)
            {
                var $field = $('<div></div>')
                        .addClass('field')
                        .attr('data-fieldname', i)
                        .attr('data-ord', fieldDataObj.ord);
                return $field;
            }
        });

    };

})(jQuery);

I experience a major memory leak on every buildFieldItem call. If I do not call this function or make it an empty function or just do not use its parameters in its scope - there is no leak. The leak only happens in Internet Explorer (8). In other browsers it's all ok. Requesting assistance. Thanks in advance.

UPDATE

I've updated my question for better demonstrating what buildFieldItem actually does.
As soon as fieldDataObj may contain about 100 items, having these 4 lines solely causes a fountain, not a leak in IE.

UPDATE 2

I've replaced clone() and empty() with creating a new div and I also minified buildFieldItem to look like:

function buildFieldItem(i, fieldDataObj)
{
    var $field = $('<div></div>')
            .addClass('field');
    return $field;
}

Still leaking. The only pattern that doesn't leak is:

function buildFieldItem(i, fieldDataObj)
{
    var $field;
    return $field;
}

Looks like a complete nonsense. The less code in function - the smaller is an overall leak. Could it be that IE can't delete a reference to the whole function or smth like that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

沧桑㈠ 2024-12-28 20:24:32

现在您已经为我们提供了更多的 buildFieldItem() 函数体,我发现另一个可能的泄漏。

如果没有处于可运行状态的完整代码,我不能肯定地说,但是当您设置 $field.attr('data-ord', fieldDataObj.ord) 时,如果 .ord< /code> 是任何类型的 DOM 引用,您可能会将某种 DOM 引用存储为 $field 对象上的属性。当您稍后在父级上执行 .empty() 时,旧版本的 IE 中可能不会清除该引用,因此它会卡住并且无法进行垃圾收集。

这就是 jQuery 发明 .data() 方法的原因之一,因为它不在 DOM 对象上存储数据,而是使用 .empty() 自行清理> 即使在旧版本的 IE 上也可以使用该方法。由于我没有代码的工作版本,因此我不能确定这就是问题所在,但我建议将:更改

.attr('data-ord', fieldDataObj.ord);

.data('ord',  fieldDataObj.ord);

And,然后更改读取该属性的任何其他引用以使用 。 data() 来读取它。

我还认为您在这条线上遇到麻烦,因为您依赖 .clone().empty() 来完美清理所有内容(即使是 jQuery 没有创建的东西)并且永远不会在任何浏览器中泄漏任何内容:

var replacement = container.clone().empty();

最好创建一个全新的容器,因为您希望它以任何方式开始都是空的,因为这永远不会泄漏先前的数据,因为它没有任何数据:

var replacement = $("<div>");

回应您第三次披露的实际内容drawFieldElements()

现在,你的代码对我来说确实没有意义。你有这个:

        function drawFieldElements(fields, container)
        {
            var replacement = container.clone()
                                       .empty();
            $.each(fields, function(i) {
                var field;
                field = buildFieldItem(i, this);
                replacement.append(field);
            });

            container.replaceWith(replacement);
        }

你拿走你的容器。您克隆并清空它。所以,现在克隆中只剩下一个带有类的顶层。您将一堆内容附加到克隆中。然后,将原始容器替换为新的克隆容器。您只是在通过所有这些多余的 DOM 操作来解决内存泄漏问题。

为什么不直接清除原始容器并附加到它呢?然后就没有克隆,没有replaceWith。

        function drawFieldElements(fields, container)
        {
            container.empty();
            $.each(fields, function(i) {
                container.append(buildFieldItem(i, this));
            });
        }

我不知道这是否有助于解决内存泄漏问题,但清理代码以删除所有不必要的 DOM 操作始终是朝着好的方向迈出的一步。

Now that you've given us more of the buildFieldItem() function body, I see another possible leak.

Without full code in a runnable state, I can't say for sure, but when you set $field.attr('data-ord', fieldDataObj.ord), if .ord is any kind of DOM reference, you may be storing some sort of DOM reference as an attribute on the $field object. When you later do a .empty() on the parent, that reference might not get cleaned up in older versions of IE and thus it gets stuck and can't get garbage collected.

That is one of the reasons that jQuery invented the .data() method because it does not store data on the DOM object and it cleans up after itself with the .empty() method even on older versions of IE. Since I don't have a working version of the code, I can't say for sure that this is the issue, but I would suggest changing:

.attr('data-ord', fieldDataObj.ord);

to

.data('ord',  fieldDataObj.ord);

And, then change any other references that read that attribute to use .data() to read it.

I also think you're asking for trouble with this line since you're relying on both .clone() and .empty() to be perfect in cleaning up everything (even things that jQuery did not create) and never ever leak anything in any browser:

var replacement = container.clone().empty();

Much better to just create a brand new container since you want it empty to start with any ways as this can never leak prior data since it doesn't have any:

var replacement = $("<div>");

Response to your third disclosure on the actual contents of drawFieldElements():

Now, your code really isn't making sense to me. You have this:

        function drawFieldElements(fields, container)
        {
            var replacement = container.clone()
                                       .empty();
            $.each(fields, function(i) {
                var field;
                field = buildFieldItem(i, this);
                replacement.append(field);
            });

            container.replaceWith(replacement);
        }

You take your container. You clone and empty it. So, now you have only a top level with a class on it left in the clone. You append a bunch of content to the clone. Then, you replace the original container with your new cloned container. You're just asking for trouble with memory leaks with all this superfluous DOM manipulation.

Why not just clear your original container and append to it? Then there's no cloning, no replaceWith.

        function drawFieldElements(fields, container)
        {
            container.empty();
            $.each(fields, function(i) {
                container.append(buildFieldItem(i, this));
            });
        }

I have no idea if this helps with your memory leak, but cleaning up the code to remove all unnecessary DOM manipulation is always a step in a good direction.

最冷一天 2024-12-28 20:24:32

所以,我终于找到了一些时间来回到这个老问题并最终确定。我很快就解决了这个问题,但我只是很忙。

以下几行导致 IE 中的内存泄漏:

var $isObligatory = $('<input>')
    .attr('type', 'checkbox')
    .attr('name', 'is-obligatory')
    .prop('checked', isObligatory)
    .change(function() {
        settings.gFields.applied[settings.subsection][i].obligate =
            ($(this).prop('checked')) ? 1 : 0;
    });

如您所见,它在为更改事件添加处理程序时创建某种循环引用。参考IE不够智能,无法破解。

事实是,注释掉这是我的第一次尝试,但是一旦我在同一个文件中实现了可拖动功能,泄漏仍然存在,因为可拖动也是导致它的原因!这就是我为此苦苦挣扎的原因。将可拖动元素移到插件代码之外解决了这个问题。

感谢大家发表评论和回答。我很抱歉没有发布完整的代码,我认为我足够聪明,只发布有问题的行。也很抱歉回答晚了。

So, I've finally found some time to go back to this old question and finalize. I've solved the problem quite quickly, but I've just been busy.

The following lines were causing memory leaks in IE:

var $isObligatory = $('<input>')
    .attr('type', 'checkbox')
    .attr('name', 'is-obligatory')
    .prop('checked', isObligatory)
    .change(function() {
        settings.gFields.applied[settings.subsection][i].obligate =
            ($(this).prop('checked')) ? 1 : 0;
    });

As you can see, it creates some kind of circular reference when adding a handler for the change event. The reference IE is not smart enough to break.

The fact is, commenting this out was my first try, but as soon as I've also had draggable functionality implemented in the same file, the leak remained, as draggables were causing it too! That's why I was struggling with that. Moving draggables outside the plugin code solved it.

Thanks everybody for posting comments and answers. I am sorry for not posting the full code, I thought I was smart enough to post only problematic lines. Sorry for late answer too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文