JavaScript 内存泄漏

18

我正在使用jQuery,类似这样的操作:

DOM

<div id="parent"></div>

JS

var _doSomeThing = function()
{
    //some codes
}

$(function()
{
    // appending div and binding methods to span
    $('#parent').append('<span>1</span>');
    $('#parent').append('<span>2</span>');
    $('#parent span').bind('click', _doSomeThing);
});

function _clearDiv()
{
    //clear div
    $('#parent').html('');
}

//sometime in future, call clear div
_clearDiv();

我的问题是,将事件绑定到DOM,然后仅从DOM中删除元素是否会导致内存泄漏?

如果是的话,该如何解决这个问题?

5个回答

19

jQuery的html方法试图通过删除调用.html('')后作为结果被删除的任何元素的事件处理程序来预防内存泄漏。

来自1.4.2版本的源代码。

html: function( value ) {
    if ( value === undefined ) {
        return this[0] && this[0].nodeType === 1 ?
        this[0].innerHTML.replace(rinlinejQuery, "") :
            null;
    } 
    // See if we can take a shortcut and just use innerHTML
    // THE RELEVANT PART
    else if ( typeof value === "string" && !rnocache.test( value ) &&
        (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
        !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

        value = value.replace(rxhtmlTag, fcloseTag);

        try {
            for ( var i = 0, l = this.length; i < l; i++ ) {
                // Remove element nodes and prevent memory leaks
               if ( this[i].nodeType === 1 ) {
                   jQuery.cleanData( this[i].getElementsByTagName("*") );
                   this[i].innerHTML = value;
                }
            }

        // If using innerHTML throws an exception, use the fallback method
        } 
        catch(e) {
            this.empty().append( value );
        }
    } 
    else if ( jQuery.isFunction( value ) ) {
        this.each(function(i){
            var self = jQuery(this), old = self.html();
            self.empty().append(function(){
                return value.call( this, i, old );
            });
        });

    }
    else {
        this.empty().append( value );
    }
    return this;
}

我们可以看到调用了jQuery.cleanData()函数。以下是该函数的源代码

cleanData: function( elems ) {
    var data, id, cache = jQuery.cache,
        special = jQuery.event.special,
        deleteExpando = jQuery.support.deleteExpando;

    for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
        id = elem[ jQuery.expando ];

        if ( id ) {
            data = cache[ id ];

            if ( data.events ) {
                for ( var type in data.events ) {
                    if ( special[ type ] ) {
                        jQuery.event.remove( elem, type );

                    } else {
                        removeEvent( elem, type, data.handle );
                    }
                }
            }

            if ( deleteExpando ) {
                delete elem[ jQuery.expando ];

            } else if ( elem.removeAttribute ) {
                elem.removeAttribute( jQuery.expando );
            }

            delete cache[ id ];
        }
    }
}
这会在jQuery.cache对象中查找与每个将被调用.html('')删除的元素相关联的数据对象的事件对象属性中的任何事件类型属性,并将其移除。
简单来说,当使用jQuery将函数绑定为元素上引发事件的处理程序时,会将一个数据对象添加为jQuery.cache对象的属性。此数据对象包含一个events属性对象,该对象将创建一个属性,该属性的名称与要绑定事件处理程序函数的事件类型匹配。这个属性将包含一个应在元素上引发事件时调用的函数数组,所以事件处理程序函数将添加到此数组中。如果这是问题中首个事件类型和元素的事件处理程序函数,则使用addEventListener/attachEvent将注册带有调用apply的jQuery.event.handle函数(使用元素作为上下文,使得在函数执行上下文中的this将指向元素)。
当引发事件时,jQuery.event.handle函数将调用与事件类型和引发事件的元素匹配的数据对象的events属性对象中数组上的所有函数。
因此,总结一下,html('')不应该导致内存泄漏,因为有多重防御措施可防止它们的发生。

@russ 我认为你是对的,昨天我进行了大量测试并得出结论,.html('')也可以避免内存泄漏,而JS的innerHTML=''会导致内存泄漏。 我想知道jQuery是否会在我们调用$()bind()函数(即绑定某些事件)时创建一个内部元素列表,以便在window.unload时删除这些事件,还是jQuery会自动为我们完成这个操作。有人知道吗? - Praveen Prasad
jQuery会在初始脚本执行时自动绑定一个函数到window.onunload以移除事件处理程序。你可以在1.4.2源代码中,在Sizzle代码块之前找到相关的代码 - http://code.jquery.com/jquery-1.4.2.js :) - Russ Cam
你是正确的,即使是 jQuery 1.3.2 版本也会在窗口卸载时清除绑定到元素上的事件。因此,我认为我们不必编写代码来清除绑定的事件以避免内存泄漏。谢谢。 - Praveen Prasad
在页面刷新/重新加载时,难道不是所有东西都被垃圾回收了吗???这应该是正常行为,无论是否使用jQuery...对吧? 这就是为什么在SPA应用程序中必须更加小心内存泄漏的原因...对吧?! - Legends
@Legends GC在页面刷新/重新加载时非常依赖于浏览器及其对DOM和JavaScript引擎交互的实现,例如http://javascript.crockford.com/memory/leak.html。 - Russ Cam

2

关于泄漏问题我无法发表评论,但您可以使用.empty()代替.html('')。这样你就可以清除innerHTML并删除任何绑定的事件处理程序。


2
是的,因为jQuery维护一个附加事件处理程序的列表,以便更轻松地取消它们,并在页面卸载时显式地为您取消它们(这可以解决IE中更严重的内存泄漏问题)。(Prototype也是如此,不能代表其他库。)解决方案是在删除元素之前取消它们(直接或通过empty)。

1

您可以始终使用$('#parent span').unbind();以确保。


0

由于您不断地引用$('#parent'),因此应在全局范围内创建对该对象的引用,以便jQuery不会在每个请求中不断查找该对象。通过这样做,您实际上是缓存了对该对象的引用,这将极大地减少内存使用。

_parent = $('#parent');

...

function(){ _parent.append('<span>1</span>'); }

编辑:我从jQuery性能规则这篇文章中学到了这个技巧。


1
你确定吗?你之前有对内存使用情况进行过分析以验证这个说法吗? - PatrikAkerstrand
我在一个jQuery应用程序中遇到了很多内存泄漏问题,以至于MSIE会在10分钟后自动关闭。经过一次调整后,这些泄漏问题消失了,内存稳定在一个合理的水平上。 - Adrian J. Moreno
3
我不会将未缓存jQuery对象称为"内存泄漏",但这种做法可能导致性能下降,听起来你在IE上遇到了这个问题。与其他浏览器相比,IE家族一般实现的本地方法较少(包括"选择器API"、"document.getElementsByClassName"),因此需要依靠JavaScript DOM遍历实现相同的结果。我建议通过尽可能将缓存jQuery对象的变量作用域限制在局部范围内,避免污染全局命名空间。 - Russ Cam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接