Подтвердить что ты не робот

Как работает очистка DOM Node в d3?

Я работаю над визузализацией графика, используя D3 в режиме позвоночника. Я разрешаю пользователю изменять размер графика, плавно переходить с использованием преобразований webkit и перерисовывать при выпуске. Чтобы код был прост, я просто перерисовываю график в новом масштабе, вместо того, чтобы пересчитывать новые позиции и размеры элементов (это был мой оригинальный подход, но моя команда запросила маршрут перерисовки).

[Я разговаривал с Бостоком через твиттер. На самом деле это не самый предпочтительный способ делать вещи]

Вещь, которую я замечаю, заключается в том, что для каждого перерисовки я сбрасываю тонны узлов, которые не очищаются.

Это не связано с циклическими ссылками в обработчиках/закрытиях событий, поскольку я отключил все, кроме моих меток (они не привязаны к обработчикам), и происходит такое же поведение.

Я попытался агрессивно удалить элементы из графика, но узлы dom все еще течет.

Вот какой код. 'render' вызывается для нового набора меток. После завершения масштабирования "close" вызывается на старом графике, а новый создается с помощью другого представления и вызывает "render":

render: function() {

        // create the svg offscreen/off dom
        //document.createElementNS(d3.ns.prefix.svg, "svg")
        var svg = this.svg = d3.select(this.el)
            .append("svg:svg")
            .attr('width', this.VIEW_WIDTH)
            .attr('height', this.VIEW_HEIGHT)

        this._drawTimeTicks.call(this, true);
        return this;
    },



_drawTimeTicks: function(includeLabels) {
    var bounds = this.getDayBounds();
    var min = bounds.start;
    var date = new Date(min);
    var hour = 1000 * 60 * 60;
    var hourDiff = 60 * this.SCALE;
    var graphX = (date.getTime() - min) / 1000 / 60;
    var textMargin = 7;
    var textVert = 11;

    // Using for loop to draw multiple vertical lines
    // and time labels.

    var timeTicks = d3.select(this.el).select('svg');
    var width = timeTicks.attr('width');
    var height = timeTicks.attr('height');

    for (graphX; graphX < width; graphX += hourDiff) {
        timeTicks.append("svg:line")
            .attr("x1", graphX)
            .attr("y1", 0)
            .attr("x2", graphX)
            .attr("y2", height)
            .classed('timeTick');

        if (includeLabels) {
            timeTicks.append("svg:text")
                .classed("timeLabel", true)
                .text(this.formatDate(date))
                .attr("x", graphX + textMargin)
                .attr("y", textVert);
        }

        date.setTime(date.getTime() + hour);
    }



close: function() {
        console.log("### closing the header");
        this.svg.selectAll('*').remove();
        this.svg.remove();
        this.svg = null;
        this.el.innerHTML = '';

        this.unbind();
        this.remove();
    }

Как вы можете видеть, я не делаю ничего сложного с обработчиками событий или закрытием. С помощью нескольких взаимодействий масштабирования я могу протекать десятки узлов dom, которые никогда не восстанавливаются GC.

Является ли это утечкой памяти или d3 делает что-то за кулисами, чтобы оптимизировать построение/обновление будущего графика? Есть ли лучший способ уничтожить график, о котором я не знаю?

Любые идеи?

4b9b3361

Ответ 1

D3 не содержит никаких скрытых ссылок на ваши узлы, поэтому нет внутренней концепции очистки DOM node. Существуют просто выборки, которые представляют собой массивы элементов DOM и самого DOM. Если вы удалите элемент из DOM, и вы не сохраните никаких дополнительных ссылок на него, он должен быть исправлен сборщиком мусора.

(Помимо этого, неясно, является ли утечка, о которой вы говорите, это элементы, оставшиеся в DOM или потерянные элементы, которые не были возвращены сборщиком мусора. В прошлом у некоторых старых браузеров были ошибки, связанные с сборкой мусора круговых ссылок между элементами DOM и закрытие JavaScript, но я не знаю о таких проблемах, которые затрагивают современные браузеры.)

Если вы обновляете DOM, наиболее эффективный способ сделать это (обычно) с использованием объединения данных, поскольку это позволяет повторно использовать существующие элементы и изменять только атрибуты, которые необходимо изменить. Использование ключевой функции для объединения данных также является хорошей идеей для постоянства объекта; если одни и те же данные отображаются как до, так и после обновления, вам может не потребоваться повторная компромисс всех его атрибутов, и браузер делает меньше работы для обновления.

В некоторых случаях существуют альтернативы произвольным обновлениям, которые быстрее, например, обновлять атрибут преобразования окружающего G-элемента, а не обновлять позиции элементов-потомков. В качестве другого примера вы можете выполнить геометрическое масштабирование в элементе SVG, просто изменив атрибут viewBox. Но геометрическое масштабирование - очень ограниченный случай; в общем, наиболее эффективное обновление зависит от того, что именно меняется. Используйте объединение данных, чтобы вы могли свести к минимуму количество добавляемых или удаляемых элементов и свести к минимуму количество необходимых вам атрибутов.

Еще пару вещей, которые я укажу...

  • Вы можете использовать объединение данных для создания нескольких тиков одновременно, вместо использования цикла for. Для циклов почти никогда не используются с D3, поскольку объединение данных может создавать несколько элементов (и иерархических структур) без циклов. Еще лучше используйте компонент оси (d3.svg.axis) и шкалу времени (d3.time.scale) с форматом времени (d3.time.format).

  • В последних версиях D3 не требуется пространство имен "svg:", поэтому вы можете добавить "текст", "строка" и т.д.

  • Я не могу думать о ситуации, когда selectAll("*").remove() имеет смысл. Селектор "*" соответствует всем потомкам, поэтому это приведет к удалению каждого отдельного потомка из его родителя. Вы всегда должны пытаться удалить самый верхний элемент - контейнер SVG, а не избыточные удаления дочерних элементов.