如何避免 jQuery 的内存泄漏?
How to avoid memory leaks from jQuery?
jQuery 在其内部缓存中保存对 DOM 节点的引用,直到我显式调用 $.remove()。如果我使用诸如 React 之类的框架,它会自行删除 DOM 节点(使用本机 DOM 元素 API),我该如何清理 jQuery 的内存缓存?
我正在使用 React 设计一个相当大的应用程序。对于那些不熟悉的人,React 将拆除 DOM 并根据需要根据自己的 "shadow" DOM 表示进行重建。该部件运行良好,没有内存泄漏。
一闪而过,我们决定使用 jQuery 插件。在 React 运行其渲染循环并构建 DOM 之后,我们初始化插件,这会导致 jQuery 持有对相应 DOM 节点的引用。稍后,用户更改页面上的选项卡,React 删除那些 DOM 元素。不幸的是,因为 React 不使用 jQuery 的 $.remove() 方法,jQuery 维护对那些 DOM 元素的引用并且垃圾收集器永远不会清除它们。
有什么方法可以告诉 jQuery 刷新缓存,或者更好的是,根本不缓存?我希望仍然能够利用 jQuery 的插件和跨浏览器优势。
当用户退出选项卡时如何执行此操作:
for (x in window) {
delete x;
}
不过这样做要好得多:
for (i in $) {
delete i;
}
jQuery 通过 internal API [= 跟踪事件和其他类型的数据20=] 但是由于此方法是内部方法,因此没有官方支持。
内部方法具有以下签名:
jQuery._data( DOMElement, data)
因此,例如,我们将检索附加到元素的所有事件处理程序(通过 jQuery):
var allEvents = jQuery._data( document, 'events');
此 returns 和 Object
包含 事件类型 作为键,以及一个 事件处理程序数组 作为价值。
现在如果你想获取特定类型的所有事件处理器,我们可以这样写:
var clickHandlers = (jQuery._data(document, 'events') || {}).click;
returns "click" 事件处理程序的 Array
或 undefined
如果指定的事件未绑定到元素。
为什么我要说这个方法?因为它允许我们跟踪事件委托和直接附加的事件监听器,所以我们可以找出 事件处理程序是否多次绑定 到同一个元素,从而导致 内存泄漏 .
但是如果你也想要没有jQuery的类似功能,你可以用方法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());
});
}
使用此功能非常简单:
writeEventHandlers(window, "resize");
我编写了一些实用程序,使我们能够跟踪绑定到 DOM 元素的事件
如果您关心性能,您会发现以下链接很有用:
- Leaking Memory in Single Page Apps
- Writing Fast, Memory-Efficient JavaScript
- JavaScript Memory Profiling
我鼓励任何读到这篇文章的人 post,注意我们代码中的内存分配,我了解到性能问题的发生是由于三件重要的事情:
- 内存
- 内存
- 是的,内存。
活动:良好做法
最好创建命名函数以便 绑定 和 解除绑定 事件处理程序 来自 DOM 个元素。
如果您正在动态创建 DOM 元素,例如,向某些事件添加处理程序,您可以考虑使用 事件委托 而不是直接绑定事件侦听器对于每个元素,这样,动态添加元素的父元素将处理该事件。此外,如果您使用 jQuery,您可以为事件命名空间 ;)
//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);
循环引用
虽然 circular references are not a problem anymore for those browsers that implement the Mark-and-sweep algorithm 在他们的 垃圾收集器 中,但如果我们要交换数据,使用那种对象并不是明智的做法,因为不可能(目前)序列化为 JSON,但在未来的版本中,由于处理此类对象的新算法,这将成为可能。让我们看一个例子:
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"
现在让我们尝试另一个例子
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"
PD:这篇文章是关于 Cloning Objects in JavaScript. Also this gist contain demos about cloning objects with circular references: clone.js
重用对象
让我们遵循一些编程原则,DRY (不要重复自己) 而不是创建具有类似功能的新对象,我们可以用一种奇特的方式抽象它们。在这个例子中,我将重用一个事件处理程序(再次使用事件)
//the usual way
function onShowContainer(e) {
$("#container").show();
}
function onHideContainer(e) {
$("#container").hide();
}
$("#btn1").on("click.btn1", onShowContainer);
$("#btn2").on("click.btn2", onHideContainer);
//the good way, passing data to events
function onToggleContainer(e) {
$("#container").toggle(e.data.show);
}
$("#btn1").on("click.btn1", { show: true }, onToggleContainer);
$("#btn2").on("click.btn2", { show: false }, onToggleContainer);
并且有很多方法可以改进我们的代码,对性能产生影响,并防止内存泄漏。在此 post 中,我主要谈到了 事件 ,但还有其他方法可以产生内存泄漏。我建议阅读之前 post 编辑的文章。
祝您阅读愉快,编码愉快!
如果您的插件公开了一种方法来以编程方式销毁它的一个实例(即 $(element).plugin('destroy')
),您应该在组件的 componentWillUnmount
生命周期中调用它。
componentWillUnmount
在您的组件从 DOM 卸载之前被调用,它是清理您的组件可能具有的所有外部引用/事件侦听器/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 组件中 创建 DOM 带有 jQuery 的元素,那么您就犯了严重的错误:您几乎应该从不 在使用 React 时需要 jQuery,因为它已经抽象出使用 DOM.
的所有痛点
我也会对使用 refs 持谨慎态度。真正需要 refs 的用例很少,而且这些用例通常涉及与 manipulate/read 来自 DOM.
的第三方库集成
如果您的组件有条件地呈现受您的 jQuery 插件影响的元素,您可以使用回调引用来监听它的 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>
);
},
});
jQuery 在其内部缓存中保存对 DOM 节点的引用,直到我显式调用 $.remove()。如果我使用诸如 React 之类的框架,它会自行删除 DOM 节点(使用本机 DOM 元素 API),我该如何清理 jQuery 的内存缓存?
我正在使用 React 设计一个相当大的应用程序。对于那些不熟悉的人,React 将拆除 DOM 并根据需要根据自己的 "shadow" DOM 表示进行重建。该部件运行良好,没有内存泄漏。
一闪而过,我们决定使用 jQuery 插件。在 React 运行其渲染循环并构建 DOM 之后,我们初始化插件,这会导致 jQuery 持有对相应 DOM 节点的引用。稍后,用户更改页面上的选项卡,React 删除那些 DOM 元素。不幸的是,因为 React 不使用 jQuery 的 $.remove() 方法,jQuery 维护对那些 DOM 元素的引用并且垃圾收集器永远不会清除它们。
有什么方法可以告诉 jQuery 刷新缓存,或者更好的是,根本不缓存?我希望仍然能够利用 jQuery 的插件和跨浏览器优势。
当用户退出选项卡时如何执行此操作:
for (x in window) {
delete x;
}
不过这样做要好得多:
for (i in $) {
delete i;
}
jQuery 通过 internal API [= 跟踪事件和其他类型的数据20=] 但是由于此方法是内部方法,因此没有官方支持。
内部方法具有以下签名:
jQuery._data( DOMElement, data)
因此,例如,我们将检索附加到元素的所有事件处理程序(通过 jQuery):
var allEvents = jQuery._data( document, 'events');
此 returns 和 Object
包含 事件类型 作为键,以及一个 事件处理程序数组 作为价值。
现在如果你想获取特定类型的所有事件处理器,我们可以这样写:
var clickHandlers = (jQuery._data(document, 'events') || {}).click;
returns "click" 事件处理程序的 Array
或 undefined
如果指定的事件未绑定到元素。
为什么我要说这个方法?因为它允许我们跟踪事件委托和直接附加的事件监听器,所以我们可以找出 事件处理程序是否多次绑定 到同一个元素,从而导致 内存泄漏 .
但是如果你也想要没有jQuery的类似功能,你可以用方法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());
});
}
使用此功能非常简单:
writeEventHandlers(window, "resize");
我编写了一些实用程序,使我们能够跟踪绑定到 DOM 元素的事件
如果您关心性能,您会发现以下链接很有用:
- Leaking Memory in Single Page Apps
- Writing Fast, Memory-Efficient JavaScript
- JavaScript Memory Profiling
我鼓励任何读到这篇文章的人 post,注意我们代码中的内存分配,我了解到性能问题的发生是由于三件重要的事情:
- 内存
- 内存
- 是的,内存。
活动:良好做法
最好创建命名函数以便 绑定 和 解除绑定 事件处理程序 来自 DOM 个元素。
如果您正在动态创建 DOM 元素,例如,向某些事件添加处理程序,您可以考虑使用 事件委托 而不是直接绑定事件侦听器对于每个元素,这样,动态添加元素的父元素将处理该事件。此外,如果您使用 jQuery,您可以为事件命名空间 ;)
//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);
循环引用
虽然 circular references are not a problem anymore for those browsers that implement the Mark-and-sweep algorithm 在他们的 垃圾收集器 中,但如果我们要交换数据,使用那种对象并不是明智的做法,因为不可能(目前)序列化为 JSON,但在未来的版本中,由于处理此类对象的新算法,这将成为可能。让我们看一个例子:
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"
现在让我们尝试另一个例子
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"
PD:这篇文章是关于 Cloning Objects in JavaScript. Also this gist contain demos about cloning objects with circular references: clone.js
重用对象
让我们遵循一些编程原则,DRY (不要重复自己) 而不是创建具有类似功能的新对象,我们可以用一种奇特的方式抽象它们。在这个例子中,我将重用一个事件处理程序(再次使用事件)
//the usual way
function onShowContainer(e) {
$("#container").show();
}
function onHideContainer(e) {
$("#container").hide();
}
$("#btn1").on("click.btn1", onShowContainer);
$("#btn2").on("click.btn2", onHideContainer);
//the good way, passing data to events
function onToggleContainer(e) {
$("#container").toggle(e.data.show);
}
$("#btn1").on("click.btn1", { show: true }, onToggleContainer);
$("#btn2").on("click.btn2", { show: false }, onToggleContainer);
并且有很多方法可以改进我们的代码,对性能产生影响,并防止内存泄漏。在此 post 中,我主要谈到了 事件 ,但还有其他方法可以产生内存泄漏。我建议阅读之前 post 编辑的文章。
祝您阅读愉快,编码愉快!
如果您的插件公开了一种方法来以编程方式销毁它的一个实例(即 $(element).plugin('destroy')
),您应该在组件的 componentWillUnmount
生命周期中调用它。
componentWillUnmount
在您的组件从 DOM 卸载之前被调用,它是清理您的组件可能具有的所有外部引用/事件侦听器/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 组件中 创建 DOM 带有 jQuery 的元素,那么您就犯了严重的错误:您几乎应该从不 在使用 React 时需要 jQuery,因为它已经抽象出使用 DOM.
的所有痛点我也会对使用 refs 持谨慎态度。真正需要 refs 的用例很少,而且这些用例通常涉及与 manipulate/read 来自 DOM.
的第三方库集成如果您的组件有条件地呈现受您的 jQuery 插件影响的元素,您可以使用回调引用来监听它的 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>
);
},
});