Обычные методы вставки работают с одним узлом. Но есть и способы вставлять множество узлов одновременно.
Оптимизация вставки в документ
Нужно сгенерировать список UL/LI.
Какой алгоритм работает быстрее?
- Сгенерировать
UL. Вставить в документ. Добавить к немуLI. - Сгенерировать
UL. Добавить к немуLI. Вставить в документ.
Разница между ними одна: в (1) вставка идёт в UL, привязанный к документу, а в (2) — нет.
Подумали? Как вы считаете?…
Почему ответ именно таков? Иногда говорят: «потому что браузер перерисовывает каждый раз при добавлении элемента». Это не так. Браузер достаточно умён, чтобы ничего не перерисовывать. В большинстве случаев процессы перерисовки и сопутствующие вычисления будут отложены до окончания работы скрипта.
Тем не менее, интуитивно можно понять, что «живой» документ — это сложная структура, связанная с CSS и с механизмом отображения («деревом рендеринга»). При вставке узла происходят разные внутренние события и обновления данных, скрытые от наших глаз.
Поэтому манипуляции с элементом, находящимся в «живом» документе, всегда медленнее, чем с элементом вне DOM.
В первую очередь это касается вставок, изменений содержания элементов и т.п.
Реальная разница зависит от операций и от размера документа — чем документ больше, тем медленнее работа с живым DOM.
Например, вот два бенчмарка.
Оба они создают таблицу 10x10, наполняя TBODY элементами TR/TD.
При этом первый вставляет все в документ тут же, второй — задерживает вставку TBODY в документ до конца процесса.
Кликните, чтобы запустить. Разница будет значительная.
/* 1. Вставляет TBODY в документ сразу. а затем элементы */
appendFirst = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table')
while(benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
// встаить TBODY и элементы
var tbody = document.createElement('TBODY');
benchTable.appendChild(tbody);
for(var i=0; i<10; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for(var j=0; j<10; j++) {
var td = document.createElement('td');
td.appendChild(document.createTextNode(''+i+j));
tr.appendChild(td);
}
}
}
}
/* 2. Полностью делает TBODY, а затем вставляет в документ */
appendLast = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table');
while(benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
var tbody = document.createElement('TBODY');
for(var i=0; i<10; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for(var j=0; j<10; j++) {
var td = document.createElement('td');
tr.appendChild(td);
td.appendChild(document.createTextNode(''+i+j));
}
}
benchTable.appendChild(tbody);
}
}
Добавление множества узлов
Рассмотрим следующую задачу, из жизни.
В документе есть список UL/LI. И тут понадобилось срочно добавить еще 20 штук LI, например, полученных с сервера. Альтернатива — таблица, и в неё нужно добавить пачку TR.
Как это сделать? Свойство innerHTML не поможет, ведь оно заменяет всех потомков, а здесь нужно лишь добавить несколько.
Очевидный вариант — appendChild для каждого из новых узлов, в цикле. Это означает N вставок в «живой» документ, где N — количество элементов.
Но, как мы видели, вставка в живой документ осуществляется небыстро. Можно ли как-то сэкономить?
Можно изъять UL из документа, вставить LI, а потом вернуть UL назад. Вариант вполне рабочий, но может быть есть что-то получше?
И как раз с этим нам помогут DocumentFragment и insertAdjacentHTML.
DocumentFragment
Итак, познакомимся с DocumentFragment. Это очень забавный кросс-браузерный DOM-объект, который похож на обычный DOM-узел, но им не является.
То есть, его можно создать:
var fragment = document.createDocumentFragment();В него можно добавлять другие узлы.
fragment.appendChild(node);
Его можно клонировать:
fragment.cloneNode(true);
..Но у него нет обычных свойств DOM-узлов, таких как innerHTML, tagName и т.п. Это не узел.
Фишка заключается в том, что когда documentFragment вставляется в DOM — то он исчезает, а вместо него вставляются его дети. Это единственное и основное свойство documentFragment по сравнению со всеми остальными сущностями DOM.
Например, можно добавить в него много LI, и потом appendChild к UL. При этом фрагмент растворится и вставятся именно LI как прямые потомки.
var fragment = document.createDocumentFragment();
for (цикл по li) {
fragment.appendChild(list[i]); // вставить каждый LI в DocumentFragment
}
ul.appendChild(fragment); // одна операция над живым документом
insertAdjacentHTML
Метод insertAdjacentHTML поддерживается всеми браузерами, кроме Firefox меньше версии 8. Он позволяет вставлять произвольный HTML в любое место документа, в том числе и между узлами!
Синтаксис:
elem.insertAdjacentHTML(where, html);
html- Строка HTML, которую нужно вставить
where- Куда по отношению к
elemвставлять строку. Всего четыре варианта:beforeBegin— передelem.afterBegin— внутрьelem, в самое начало.beforeEnd— внутрьelem, в конец.afterEnd— послеelem.
- Вставка в документ всегда медленнее, чем вставка в узел вне документа. Чем больше размер DOM документа, тем медленнее в него вставка, из-за внутренних структур и зависимостей.
- Метод
elem.insertAdjacentHTML(where, html)поддерживается всеми современными браузерами.Он позволяет вставлять HTML-текст в произвольное место документа.
Совместимость:
- В Firefox<8 его нет.
- IE6-7 поддерживают его лишь для техэлементов, где работает свойство
innerHTML(т.е. кромеTABLE,TRи т.п.).
DocumentFragmentпозволяет минимизировать количество вставок в живой документ. Этот объект похож на обычный DOM-узел, но при вставке «растворяется», и вместо него вставляются содержащиеся в нём узлы.- Проверить поддержку
insertAdjacentHTMLможно так:
if (elem.insertAdjacentHTML) { ... } - Если этот метод не поддерживается, то сделайте временный элемент, через
innerHTMLпоставьте тудаhtml, а затем переместите содержимое вDocumentFragment. Последнее действие — вставка в документ.

Например, вставим пропущенные элементы списка перед <li>5</li>:
<ul>
<li>1</li>
<li>2</li>
<li>5</li>
</ul>
<script>
var ul = document.body.children[0];
var li5 = ul.children[2];
li5.insertAdjacentHTML("beforeBegin", "<li>3</li><li>4</li>");
</script>
Единственный недостаток этого метода — он не работает в Firefox до версии 8. Но там можно воспользоваться documentFragment.
Итого
DocumentFragment и insertAdjacentHTML дополняют друг друга. Один работает с узлами, другой — с текстом. Так что оба подхода полезны.
Напишите функцию insertBeforeHTML(elem, html), которая вставляет HTML-строку html перед элементом elem, используя insertAdjacentHTML, если он поддерживается, а если нет — то через DocumentFragment.
В обоих случаях должна быть лишь одна операция с «живым» DOM.
Следующий код должен вставить два пропущенных элемента списка <li>3</li><li>4</li>:
<ul>
<li>1</li>
<li>2</li>
<li>5</li>
</ul>
<script>
var ul = document.body.children[0];
var li5 = ul.children[2];
function insertBeforeHTML(elem, html) {
/* ваш код */
}
insertBeforeHTML(li5, "<li>3</li><li>4</li>")
</script>
<ul>
<li>1</li>
<li>2</li>
<li>5</li>
</ul>
<script>
var ul = document.body.children[0];
var li5 = ul.children[2];
function insertBeforeHTML(elem, html) {
if (elem.insertAdjacentHTML) {
elem.insertAdjacentHTML("beforeBegin", html);
} else {
var fragment = document.createDocumentFragment();
var tmp = document.createElement('DIV');
tmp.innerHTML = html;
var child = tmp.firstChild;
while(child) {
var next = child.nextSibling;
fragment.appendChild(child); // child.nextSibling изменится
child = next; // но мы предварительно скопировали его в next
}
elem.parentNode.insertBefore(fragment, elem);
}
}
insertBeforeHTML(li5, "<li>3</li><li>4</li>")
</script>
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.