清理内存泄漏
Cleaning up memory leaks
我正在使用 jQuery 克隆元素,然后在该克隆中保存对元素的引用。很久以后删除克隆。这是一个基本示例:
HTML
<div> <span></span> </div>
脚本
var i, $clone, $span,
$saved = $('span'),
$orig = $('div');
for (i = 0; i < 100; i++) {
$clone = $orig.clone().appendTo('body');
$span = $clone.find('span');
$saved = $saved.add($span);
$clone.remove();
}
console.log( 'leaking = ', $saved.length);
控制台日志输出长度为101
。
我需要清理 $saved
jQuery 对象并删除对不再附加到 DOM 的元素的引用。所以我写了这个基本函数来清理它。
var cleanUpLeaks = function ($el) {
var el, remove,
index = $el.length - 1;
while (index >= 0) {
el = $el[index];
remove = true;
while (el) {
el = el.parentNode;
if (el && el.nodeName === 'HTML') {
remove = false;
break;
}
}
if (remove) {
$el.splice(index, 1);
}
index--;
}
return $el;
};
console.log( 'cleaned up = ', cleanUpLeaks( $saved ).length );
这次控制台输出1
.
所以现在我的问题是:
- 我怎样才能首先防止内存泄漏?
- 如果那不可能,我是否应该在
cleanUpLeaks
函数中使用 .splice()
来删除引用?或者按照建议将该引用设置为 null
会更好吗?因为当我将其设置为 null
时,$saved
的长度仍为 101
.
演示:http://jsfiddle.net/Mottie/6q2hjazg/
为了详细说明,我在 $saved
中保存了对跨度的引用。还有其他函数将此值用于样式等。这是一个非常基本的例子;不,我不会在将克隆附加到正文后立即删除克隆,这是为了显示内存泄漏是如何发生的。
一个可能的解决方案是,您从 AngularJS 的书和猴子补丁 jQuery 中取出一片叶子,以便在删除元素时触发事件。然后您可以为该事件添加一个处理程序并将 $saved
的状态恢复到添加 $span
.
之前的状态
首先,猴子补丁jQuery(取自AngularJS source):
// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
var originalCleanData = jQuery.cleanData;
var skipDestroyOnNextJQueryCleanData;
jQuery.cleanData = function (elems) {
var events;
if (!skipDestroyOnNextJQueryCleanData) {
for (var i = 0, elem;
(elem = elems[i]) != null; i++) {
events = jQuery._data(elem, "events");
if (events && events.$destroy) {
jQuery(elem).triggerHandler('$destroy');
}
}
} else {
skipDestroyOnNextJQueryCleanData = false;
}
originalCleanData(elems);
};
接下来,添加您的 $destroy
事件处理程序并恢复捕获的 $saved
的原始状态。
var i, $clone, $span,
$saved = $('span'),
$orig = $('div');
for (i = 0; i < 100; i++) {
(function ($originalSaved) {
$clone = $orig.clone().appendTo('body');
$span = $clone.find('span');
$clone.on('$destroy', function () {
$saved = $originalSaved;
$originalSaved = null;
});
$saved = $saved.add($span);
$clone.remove();
})($saved);
}
console.log('original length = ', $saved.length); // => 1
这是 a jsFiddle 的效果。在我 Chrome 的测试中,这不会引入额外的泄漏。
此处更好的解决方案是停止在持久性 jQuery 变量中保存动态 DOM 元素。如果您的页面定期从 DOM 中删除内容,那么将这些内容保存在持久性 jQuery 对象中只会让您不得不处理内存泄漏,而不是将设计更改为不存在的设计必须保存对 DOM 元素的引用。
相反,如果您只是使用文档中其他地方未使用的特定 class 名称标记有趣的元素,则可以随时使用简单的 jQuery 生成所需的元素列表选择器查询,您将完全没有泄漏问题,因为您永远不会在持久变量中保留 DOM 引用。
我正在使用 jQuery 克隆元素,然后在该克隆中保存对元素的引用。很久以后删除克隆。这是一个基本示例:
HTML
<div> <span></span> </div>
脚本
var i, $clone, $span,
$saved = $('span'),
$orig = $('div');
for (i = 0; i < 100; i++) {
$clone = $orig.clone().appendTo('body');
$span = $clone.find('span');
$saved = $saved.add($span);
$clone.remove();
}
console.log( 'leaking = ', $saved.length);
控制台日志输出长度为101
。
我需要清理 $saved
jQuery 对象并删除对不再附加到 DOM 的元素的引用。所以我写了这个基本函数来清理它。
var cleanUpLeaks = function ($el) {
var el, remove,
index = $el.length - 1;
while (index >= 0) {
el = $el[index];
remove = true;
while (el) {
el = el.parentNode;
if (el && el.nodeName === 'HTML') {
remove = false;
break;
}
}
if (remove) {
$el.splice(index, 1);
}
index--;
}
return $el;
};
console.log( 'cleaned up = ', cleanUpLeaks( $saved ).length );
这次控制台输出1
.
所以现在我的问题是:
- 我怎样才能首先防止内存泄漏?
- 如果那不可能,我是否应该在
cleanUpLeaks
函数中使用.splice()
来删除引用?或者按照建议将该引用设置为null
会更好吗?因为当我将其设置为null
时,$saved
的长度仍为101
.
演示:http://jsfiddle.net/Mottie/6q2hjazg/
为了详细说明,我在 $saved
中保存了对跨度的引用。还有其他函数将此值用于样式等。这是一个非常基本的例子;不,我不会在将克隆附加到正文后立即删除克隆,这是为了显示内存泄漏是如何发生的。
一个可能的解决方案是,您从 AngularJS 的书和猴子补丁 jQuery 中取出一片叶子,以便在删除元素时触发事件。然后您可以为该事件添加一个处理程序并将 $saved
的状态恢复到添加 $span
.
首先,猴子补丁jQuery(取自AngularJS source):
// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
var originalCleanData = jQuery.cleanData;
var skipDestroyOnNextJQueryCleanData;
jQuery.cleanData = function (elems) {
var events;
if (!skipDestroyOnNextJQueryCleanData) {
for (var i = 0, elem;
(elem = elems[i]) != null; i++) {
events = jQuery._data(elem, "events");
if (events && events.$destroy) {
jQuery(elem).triggerHandler('$destroy');
}
}
} else {
skipDestroyOnNextJQueryCleanData = false;
}
originalCleanData(elems);
};
接下来,添加您的 $destroy
事件处理程序并恢复捕获的 $saved
的原始状态。
var i, $clone, $span,
$saved = $('span'),
$orig = $('div');
for (i = 0; i < 100; i++) {
(function ($originalSaved) {
$clone = $orig.clone().appendTo('body');
$span = $clone.find('span');
$clone.on('$destroy', function () {
$saved = $originalSaved;
$originalSaved = null;
});
$saved = $saved.add($span);
$clone.remove();
})($saved);
}
console.log('original length = ', $saved.length); // => 1
这是 a jsFiddle 的效果。在我 Chrome 的测试中,这不会引入额外的泄漏。
此处更好的解决方案是停止在持久性 jQuery 变量中保存动态 DOM 元素。如果您的页面定期从 DOM 中删除内容,那么将这些内容保存在持久性 jQuery 对象中只会让您不得不处理内存泄漏,而不是将设计更改为不存在的设计必须保存对 DOM 元素的引用。
相反,如果您只是使用文档中其他地方未使用的特定 class 名称标记有趣的元素,则可以随时使用简单的 jQuery 生成所需的元素列表选择器查询,您将完全没有泄漏问题,因为您永远不会在持久变量中保留 DOM 引用。