如何避免jQuery的内存泄漏?

时间:2015-06-11 23:23:39

标签: javascript jquery memory-leaks reactjs

jQuery在其内部缓存中保存对DOM节点的引用,直到我显式调用$ .remove()。如果我使用像React这样的框架自己删除DOM节点(使用本机DOM元素API),我该如何清理jQuery的mem缓存?

我正在使用React设计一个相当大的应用。对于那些不熟悉的人,React将拆除DOM,并根据自己的"阴影"根据需要进行重建。 DOM表示。该部件运行良好,没有内存泄漏。

Flash转发,我们决定使用jQuery插件。在React运行其渲染循环并构建DOM之后,我们初始化插件,这会导致jQuery保存对相应DOM节点的引用。稍后,用户更改页面上的选项卡,React将删除这些DOM元素。不幸的是,因为React没有使用jQuery的$ .remove()方法,所以jQuery维护对这些DOM元素的引用,垃圾收集器永远不会清除它们。

有没有办法告诉jQuery刷新缓存,或者更好的是,根本不缓存?我希望仍然能够利用jQuery的插件和跨浏览器的优点。

2 个答案:

答案 0 :(得分:11)

jQuery 通过内部 API jQuery._data()跟踪事件和其他类型的数据,但由于此方法是内部的,因此没有官方支持

内部方法具有以下签名:

jQuery._data( DOMElement, data)

因此,例如,我们将检索附加到Element的所有事件处理程序(通过jQuery):

var allEvents = jQuery._data( document, 'events');

这会返回包含事件类型Object作为键,以及事件处理程序数组作为值。

现在,如果您想获取特定类型的所有事件处理程序,我们可以编写如下:

var clickHandlers = (jQuery._data(document, 'events') || {}).click;

如果指定的事件未绑定到Element,则返回“click”事件处理程序Array的{​​{1}}。

为什么我会谈论这种方法?因为它允许我们追踪事件委托和直接附加的事件监听器,以便我们可以找出是否< strong>事件处理程序多次绑定到同一个元素,导致内存泄漏

但如果您还想要一个没有jQuery的类似功能,可以使用方法 undefined

来实现

看一下这篇有用的文章:

调试

我们将编写一个简单的函数来打印事件处理程序及其命名空间(如果已指定)

getEventHandlers

使用此功能非常简单:

function writeEventHandlers (dom, event) {
    jQuery._data(dom, 'events')[event].forEach(function (item) {
        console.info(new Array(40).join("-"));
        console.log("%cnamespace: " + item.namespace, "color:orangered");
        console.log(item.handler.toString());
    });
}

我编写了一些实用程序,允许我们跟踪绑定到DOM Elements的事件

如果您关心表现,您会发现以下链接很有用:

我鼓励任何读过这篇文章的人,在我们的代码中注意内存分配,因为三个重要的事情我学习了性能问题:

  1. 内存
  2. 内存
  3. 是的,记忆。
  4. 事件:良好做法

    创建命名函数是一个好主意,以便从DOM元素中绑定解除绑定 事件处理程序

    如果要动态创建DOM元素,例如,在某些事件中添加处理程序,可以考虑使用事件委派而不是将事件侦听器直接绑定到每个元素,这样,父级动态添加的元素将处理该事件。此外,如果您使用的是jQuery,则可以命名事件;)

    writeEventHandlers(window, "resize");
    

    循环参考

    虽然 circular references 对于那些在垃圾收集器中实现Mark-and-sweep algorithm的浏览器不再是问题,但这不是明智之举如果我们正在交换数据,则使用这种对象,因为不可能(现在)序列化为JSON,但在将来的版本中,由于处理这种对象的新算法,它是可能的。我们来看一个例子:

    //the worse!
    $(".my-elements").click(function(){});
    
    //not good, anonymous function can not be unbinded
    $(".my-element").on("click", function(){});
    
    //better, named function can be unbinded
    $(".my-element").on("click", onClickHandler);
    $(".my-element").off("click", onClickHandler);
    
    //delegate! it is bound just one time to a parent element
    $("#wrapper").on("click.nsFeature", ".my-elements", onClickMyElement);
    
    //ensure the event handler is not bound several times
    $("#wrapper")
        .off(".nsFeature1 .nsFeature2") //unbind event handlers by namespace
        .on("click.nsFeature1", ".show-popup", onShowPopup)
        .on("click.nsFeature2", ".show-tooltip", onShowTooltip);
    

    现在让我们试试另一个例子

    var o1 = {};
        o2 = {};
    o1.a = o2; // o1 references o2
    o2.a = o1; // o2 references o1
    
    //now we try to serialize to JSON
    var json = JSON.stringify(o1);
    //we get:"Uncaught TypeError: Converting circular structure to JSON"
    

    PD:这篇文章是关于Cloning Objects in JavaScript的。此要点还包含有关使用循环引用克隆对象的演示:clone.js

    重用对象

    让我们按照一些编程原则, DRY (不要重复自己)而不是创建具有类似功能的新对象,我们可以抽象地抽象它们办法。在这个例子中,我将重用一个事件处理程序(再次使用事件)

    var freeman = {
        name: "Gordon Freeman",
        friends: ["Barney Calhoun"]
    };
    
    var david = {
        name: "David Rivera",
        friends: ["John Carmack"]
    };
    
    //we create a circular reference
    freeman.friends.push(david); //freeman references david
    david.friends.push(freeman); //david references freeman
    
    //now we try to serialize to JSON
    var json = JSON.stringify(freeman);
    //we get:"Uncaught TypeError: Converting circular structure to JSON"
    
    //the usual way
    function onShowContainer(e) {
        $("#container").show();
    }
    function onHideContainer(e) {
        $("#container").hide();
    }
    $("#btn1").on("click.btn1", onShowContainer);
    $("#btn2").on("click.btn2", onHideContainer);
    

    有很多方法可以改进我们的代码,对性能产生影响,并防止内存泄漏。在这篇文章中,我主要讨论了事件,但还有其他方法可以产生内存泄漏。我建议阅读之前发布的文章。

    快乐的阅读和快乐的编码!

答案 1 :(得分:3)

如果您的插件公开了一种方法来以编程方式销毁其中一个实例(即$(element).plugin('destroy')),那么您应该在组件的componentWillUnmount生命周期中调用它。

在从DOM卸载组件之前调用

componentWillUnmount,它是清理组件在其生命周期内可能创建的所有外部引用/事件侦听器/ dom元素的正确位置。

var MyComponent = React.createClass({
    componentDidMount() {
        $(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin();
    },
    componentWillUnmount() {
        $(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin('destroy');
    },
    render() {
        return <div ref="jqueryPluginContainer" />;
    },
});

如果您的插件没有公开自行清理的方法,this article列出了一些方法,您可以尝试取消引用一个经过深思熟虑的插件。

但是,如果你在你的React组件中使用jQuery 创建 DOM元素,那么你就是在做一些严重的错误:在使用时你应该从不需要jQuery React,因为它已经抽象出了使用DOM的所有痛点。

我也要警惕使用refs。实际上只需要很少的用例,而这些用例通常涉及与操作/读取DOM的第三方库集成。

如果你的组件有条件地渲染受jQuery插件影响的元素,你可以使用callback refs来监听它的mount / unmount事件。

之前的代码将成为:

var MyComponent = React.createClass({
    handlePluginContainerLifecycle(component) {
        if (component) {
            // plugin container mounted
            this.pluginContainerNode = React.findDOMNode(component);
            $(this.pluginContainerNode).plugin();
        } else {
            // plugin container unmounted
            $(this.pluginContainerNode).plugin('destroy');
        }
    },
    render() {
        return (
            <div>
                {Math.random() > 0.5 &&
                    // conditionally render the element
                    <div ref={this.handlePluginContainerLifecycle} />
                }
            </div>
        );
    },
});