spam poison

DOM DocumentFragment: быстрее быстрого

Эта статья на других языках: English

Использование DocumentFragment и сравние его быстродействия с обычным appendChild. Мои комментарии далее курсивом.


Недавно я игрался с DOM DocumentFragment в JavaScript, пробуя, что же можно с ним сотворить. Если вкратце, то DocumentFragment является облегченным контейнером для DOM-узлов. Он является частью спецификации DOM 1 и поддерживается во всех современных браузерах (был добавлен в Internet Explorer в 6 версии).

В процессе чтения я наткнулся на интересную вещь. Цитирую из спецификации:

Также различные операции — например, добавление узлов как дочерних для другого Node — могут принимать в качестве аргумента объекты DocumentFragment; в результате этого все дочерние узлы данного DocumentFragment перемещаются в список дочерних узлов текущего узла.

Это означает, что если у вас есть группа DOM-узлов, которые вы добавляете к фрагменту документа, то после этого можно этот фрагмент просто добавить к самому документу (вы можете добиться того же самого результата, если добавите каждый узел к документу в индивидуальном порядке). Я тут же почувствовал возможный выигрыш в производительности. Я немного исследовал этот момент и обнаружил, что DocumentFragment также поддерживает метод cloneNode. Это обеспечивает нас полной функциональностью для экстремальной оптимизации процесса добавления узла в DOM-дерево.

Я создал простую демо-версию для проверки этой теории.

Давайте рассмотрим ситуацию, когда у вас есть группа узлов, которую нужно добавить к DOM-дереву документа (в демо-версии это 12 узлов — 8 на верхнем уровне — против целой кучи div).

var elems = [
        document.createElement("hr"),
        text( document.createElement("b"), "Links:" ),
        document.createTextNode(" "),
        text( document.createElement("a"), "Link A" ),
        document.createTextNode(" | "),
        text( document.createElement("a"), "Link B" ),
        document.createTextNode(" | "),
        text( document.createElement("a"), "Link C" )
];

function text(node, txt){
        node.appendChild( document.createTextNode(txt) );
        return node;
}

Нормальное добавление

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

var div = document.getElementsByTagName("div");

for ( var i = 0; i < div.length; i++ ) {
        for ( var e = 0; e < elems.length; e++ ) {
                div[i].appendChild( elems[e].cloneNode(true) );
        }
}

Добавление при помощи DocumentFragment

Однако, если мы будем использовать DocumentFragment для совершения тех е самых операций, то ситуация изменится. Для начала мы добавим все наши узлы к самому фрагменту (используя имеющийся метод createDocumentFragment).

Самое интересное начинается тогда, когда мы собираемся добавить сами узлы в документ: нам нужно вызвать по одному разу appendChild и cloneNode для всех узлов!

var div = document.getElementsByTagName("div");
var fragment = document.createDocumentFragment();

for ( var e = 0; e < elems.length; e++ ) {
        fragment.appendChild( elems[e] );
}

for ( var i = 0; i < div.length; i++ ) {
        div[i].appendChild( fragment.cloneNode(true) );
}

При проведении замеров времени можно увидеть следующую картину:

Браузер Нормальный (ms) Fragment (ms)
Firefox 3.0.1 90 47
Safari 3.1.2 156 44
Opera 9.51 208 95
IE 6 401 140
IE 7 230 61
IE 8b1 120 40

В качестве заключения: метод, который игнорируется в современной веб-разработке, может привести к значительному ускорению (в 2–3 раза) работы ваших преобразований DOM-дерева.

А если еще быстрее?

Далее мои 5 копеек. Я подумал: зачем нам каждый раз создавать фрагмент документа, если мы для этой цели можем использовать обычный его узел (фактически, создавать кэш нашего узла, который мы собираемся везде менять)? Так я пришел к следующему фрагменту кода:

var div = document.getElementsByTagName("div");
    var dv = document.createElement("div");
    var parent = div[0].parentNode;

    for ( var e = 0; e < elems.length; e++ ) {
	dv.appendChild( elems[e].cloneNode(true) );
    }

    for ( var i = 0; i < div.length; i++ ) {
// for IE
	parent.replaceChild(dv.cloneNode(true),div[i]);
// for other
	div[i] = dv.cloneNode(true);
    }

В нем соответствующие узлы документа заменяются на клон кэшированной версии (без создания DocumentFragemnt). Это работает еще быстрее (везде, кроме IE — примерно на порядок, в IE — в полтора–два раза).



Информация о статье
Написана: Mon, 21 Jul 2008 21:12:00 EEST
Авторы: John Resig
Добавил(а): AlexParamonov at Sat, 20 Dec 2008 21:13:03 EET
Изменил(а): AlexParamonov at Sat, 20 Dec 2008 21:25:33 EET
Информация о переводе
Переведено: Tue, 22 Jul 2008 21:22:00 EEST
Язык оригинала: English
Эта статья на других языках: English
Перевод: Николай Мациевский a.k.a sunnybear

Добавил(а): AlexParamonov at Sat, 20 Dec 2008 21:24:57 EET
Изменил(а): AlexParamonov at Sat, 20 Dec 2008 21:28:07 EET
Ссылки по теме
Copyright © 2008-2010 Alexander Paramonov
Valid XHTML 1.0 Transitional