【发布时间】:2015-03-07 23:34:32
【问题描述】:
如果事件处理闭包引用使用键控数据连接的 d3.js 选择,似乎会导致 DOM 节点泄漏。
为什么会这样?是 d3.js 的问题还是调用方式的问题?
这个例子在step被重复调用时泄漏HTMLLIElement对象(clickHandler不必被执行):
function getKeys(n) {
// returns a random array of n unique Strings, e.g. ["Alpha", "Quebec", "Charlie"]
}
function step() {
function clickHandler() {
// removing this reference removes the leak
// (note that the outer variable is pulled into closure scope regardless of whether this function is called).
listItems;
}
var keys = getKeys(3);
var listItems = d3.selectAll('li')
.data(keys, function(d) { return d });
listItems.enter()
.append('li')
.text(function(d) { return '#' + d })
.on('click', clickHandler)
listItems.exit()
.remove()
}
此模式可在 D3.js 3.5.3 中重现,并可在 Chrome 39 中识别。
当满足两个条件时,似乎 DOM 节点被泄露:
- 选择有按键功能
- 一个闭包,用作选择中的一个节点的事件处理程序,具有对外部范围选择的引用。不必执行闭包。
任何这些步骤都可以防止内存泄漏:
- 在对
data的调用中未使用键函数 - 在
step末尾添加listItems = null - 避免在闭包中引用外部选择
- 在点击处理闭包中添加
listItems = null。
后一点特别有趣,因为它释放了所有的泄露节点,而不仅仅是当前listItems 选择中的节点。这意味着选择是链接的,这是我没想到的。
在 Chrome DevTools 中检查堆快照显示泄露的 HTMLLIElement 对象在其保留器层次结构中有两个不同的 listItems:
这是预期的行为吗?如果是这样,是什么原因造成的?这是我的代码或 d3.js 中的内存泄漏吗?
【问题讨论】:
-
注:我发现这是一个逻辑错误的一部分,我通过避免关闭引用来修复它。我不提倡这种模式(因为它可能会导致内存泄漏),但我不了解这种特定情况下的行为,因此提出了问题。
标签: javascript d3.js memory-leaks