Примечание: ниже перевод заметки от John Resig DOM "DOM DocumentFragments", в которой автор рассказывает об опыте использования 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 — в полтора–два раза).