Быстрый DOM

Работа с DOM-деревом в JavaScript является самым проблематичным местом. Его можно сравнить только разве что с базой данных для серверных приложений. Если JavaScript выполняется очень долго, скорее всего, дело именно в DOM-методах. Ниже рассмотрено несколько прикладных моментов, то есть способов максимально ускорить этот «затор».

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

DocumentFragment является облегченным контейнером для DOM-узлов. Он описан в спецификации DOM1 и поддерживается во всех современных браузерах (был добавлен в 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»);
( 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) );
}

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

Браузер

Нормальный

Fragment

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

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

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

var div = document.getElementsByTagName(«div»);
var child = document.createElement(«div»);
var parent = div[0].parentNode;

for ( var e = 0; e < elems.length; e++ ) {
     child.appendChild( elems[e].cloneNode(true) );
}
for ( var i = 0; i < div.length; i++ ) {
// для IE 
     if (IE) { 
          parent.replaceChild(child.cloneNode(true),div[i]);
// для других браузеров 
     } else { 
          div[i] = child.cloneNode(true); 
     }
}

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

innerHTML нам поможет

 Чтобы уменьшить отрисовку отдельных частей документа в процессе добавления какого-либо большого фрагмента, мы можем сохранять HTML-код в виде текста и лишь на финальном этапе вставлять его в DOM-дерево. Давайте рассмотрим следующий пример:

var i, j, el, table, tbody, row, cell;
el = document.createElement(«div»);
document.body.appendChild(el); table = document.createElement(«table»);
el.appendChild(table);
tbody = document.createElement(«tbody»);
table.appendChild(tbody);
for (i = 0; i < 1000; i++) { 
     row = document.createElement(«tr»); 
     for (j = 0; j < 5; j++) { 
          cell = document.createElement(«td»); 
          row.appendChild(cell); 
     } 
     tbody.appendChild(row);
}

Его можно значительно ускорить, если добавлять узлы не последовательно один за другим, а сначала создав HTML-строку со всем необходимым кодом, которая будет вставлена через innerHTML в конце всех операций.

В данном примере кроме уже указанного ускорения еще используется первоначальное создание массива элементов, которые можно объединить через свойство join в строку. Для больших строк это работает быстрее, чем последовательная конкатенация отдельных частей.

var i, j, el, idx, html;
idx = 0; html = [];
html[idx++] = «<table>»;
for (i = 0; i < 1000; i++) {
html[idx++] = «<tr>»;
for (j = 0; j < 5; j++) { 
     html[idx++] = «<td></td>»; 
     } 
     html[idx++] = «</tr>»;
}
html[idx++] = «</table>»;
el = document.createElement(«div»);
document.body.appendChild(el);
el.innerHTML = html.join(«»);

Posted in Разгони свой сайт.