d3.js键入的内存泄漏在事件处理程序关闭中加入

时间:2015-01-09 15:39:56

标签: javascript d3.js memory-leaks

如果事件处理闭包引用了使用键控数据连接的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()
}

JSBin

DevTools-friendly version

此模式可与D3.js 3.5.3重现,并可在Chrome 39中识别。

当满足两个条件时,DOM节点似乎泄露:

  1. 选择具有关键功能
  2. 闭包用作选择中某个节点的事件处理程序,它具有对外部作用域选择的引用。关闭不必执行。
  3. 任何这些步骤都可以防止内存泄漏:

    • data
    • 的调用中未使用关键功能
    • listItems = null
    • 末尾添加step
    • 避免在闭包中对外部选择的引用
    • 在点击处理关闭中添加listItems = null

    后一点特别有趣,因为它释放了泄漏节点的所有,而不仅仅是当前listItems选择中的那些。这意味着选择是相互关联的,这是我没想到的。

    在Chrome DevTools中检查堆快照显示泄露的HTMLLIElement对象在其保留层次结构中有两个不同的listItems

    DevTools screenshot of leaked nodes

    这是预期的行为吗?如果是这样,是什么导致的?这是我的代码或d3.js中的内存泄漏吗?

1 个答案:

答案 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的引用。