如果事件处理闭包引用了使用键控数据连接的d3.js选择,那么它似乎会导致DOM节点泄露。
为什么会这样?这是d3.js的问题还是调用它的方式?
当重复调用HTMLLIElement
时,此示例会泄漏step
个对象(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
listItems = null
step
listItems = null
。后一点特别有趣,因为它释放了泄漏节点的所有,而不仅仅是当前listItems
选择中的那些。这意味着选择是相互关联的,这是我没想到的。
在Chrome DevTools中检查堆快照显示泄露的HTMLLIElement
对象在其保留层次结构中有两个不同的listItems
:
这是预期的行为吗?如果是这样,是什么导致的?这是我的代码或d3.js中的内存泄漏吗?
答案 0 :(得分:3)
在添加新元素的输入阶段,您将绑定到每个新添加的元素' li'元素的onClick处理程序。
listItems.enter()
.append('li')
.text(function(d) { return '#' + d })
.on('click', clickHandler);
在退出阶段,您要删除' li'不再需要的元素。但是,在删除' li'之前,您不会从onClick处理程序解除绑定。元件。
在您发布的探查器图片中,请注意HTMLLIElement的颜色为红色。 Chrome的内存分析器告诉您HTMLIElement与DOM树断开连接,但仍然有一个javascript引用它。在这种情况下,' li'的onClick处理程序element有对js代码的引用。
在D3退出阶段调用.on('click',null)
删除点击处理程序。
listItems.exit()
.on('click', null)
.remove();
将删除对clickHandler的引用。