Why do my svg nodes leak memory in IE

2019-02-14 05:40发布

问题:

Background: Inspired by this question and this question about D3 memory usage I decided to dig into how it really works, and soon came upon cautions about repeated add/remove of DOM nodes in IE.

To isolate from whatever else D3 was doing, I first tried the basic SVG case of adding/removing 1000 circles every second:

var count = 1000;
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute('width', '800');
svg.setAttribute('height', '800');
document.body.appendChild(svg);

function update() {
  // remove existing circles
  var circles = svg.querySelectorAll("circle")
  for (var i = circles.length - 1; i >= 0; i--) {
    var parent = circles[i].parentNode;
    if (parent) parent.removeChild(circles[i]);
  };

  // add new ones. Yes, would make more sense to update the x,y on the existing
  // circles but this is to show what happens in IE with lots of nodes being added/removed
  for (var j = count - 1; j >= 0; j--) {
    var node = document.createElementNS("http://www.w3.org/2000/svg", 'circle');
    node.id = 'id' + Math.random();
    node.setAttributeNS(null, "cx", Math.random()*800);
    node.setAttributeNS(null, "cy", Math.random()*800);
    node.setAttributeNS(null, "r",  5);
    node.setAttributeNS(null, "fill", "blue");
    svg.appendChild(node);
  };
}

setInterval(update, 1000);

I found that this slowly leaks memory in both IE9 and IE10. See here for live version: http://bl.ocks.org/explunit/6413259

What (if anything) can I do to prevent the leak, and what implications does this have for how D3 code targeting IE should be written that adds/removes a lot of nodes?

Other notes:

Inspired by this article, I tried a simple node pool approach by pushing the removed nodes on to a stack:

if (parent) circlePool.push( parent.removeChild(circles[i]) );

And reusing them later:

var node;
if (circlePool.length == 0) {
  node = document.createElementNS("http://www.w3.org/2000/svg", 'circle');
} else {
  node = circlePool.pop();
  //TODO: clear out attributes of the node
}

But this did not make any difference.

回答1:

The only thing I've found so far that helps with IE memory usage is to not assign IDs to the nodes, or to assign them IDs that repeat across iterations of the animation:

node.id = 'id' + j;

See the live version here: http://bl.ocks.org/explunit/6413371

I thought that nulling the ID prior to removing the DOM node would do the same thing, but this had no effect.

I don't really like this answer so I'm hoping that someone else comes along with a better explanation of why the node pool approach didn't work.

But for now, the moral of the story for developers using D3: if you're repeatedly adding and then removing lots of nodes with unique IDs (e.g. from .ajax calls which get records out of db), you might be in danger of a memory leak.