# Мультивставка: insertAdjacentHTML и DocumentFragment
Обычные методы вставки работают с одним узлом. Но есть и способы вставлять множество узлов одновременно.
[cut]
## Оптимизация вставки в документ
Рассмотрим задачу: сгенерировать список `UL/LI`.
Есть две возможных последовательности:
Сначала вставить `UL` в документ, а потом добавить к нему `LI`:
```js
var ul = document.createElement('ul');
document.body.appendChild(ul); // сначала в документ
for(...) ul.appendChild(li); // потом узлы
```
Полностью создать список "вне DOM", а потом -- вставить в документ:
```js
var ul = document.createElement('ul');
for(...) ul.appendChild(li); // сначала вставить узлы
document.body.appendChild(ul); // затем в документ
```
Как ни странно, между этими последовательностями есть разница. В большинстве браузеров, второй вариант -- быстрее.
Почему же? Иногда говорят: "потому что браузер перерисовывает каждый раз при добавлении элемента". Это не так. Дело вовсе не в перерисовке.
Браузер достаточно "умён", чтобы ничего не перерисовывать понапрасну. В большинстве случаев процессы перерисовки и сопутствующие вычисления будут отложены до окончания работы скрипта, и на тот момент уже совершенно без разницы, в какой последовательности были изменены узлы.
**Тем не менее, при вставке узла происходят разные внутренние события и обновления внутренних структур данных, скрытые от наших глаз.**
Что именно происходит -- зависит от конкретной, внутренней браузерной реализации DOM, но это отнимает время. Конечно, браузеры развиваются и стараются свести лишние действия к минимуму.
[online]
### Бенчмарк [#insert-bench-tbody]
Чтобы легко проверить текущее состояние дел -- вот два бенчмарка.
Оба они создают таблицу 20x20, наполняя TBODY элементами TR/TD.
При этом первый вставляет все в документ тут же, второй -- задерживает вставку TBODY в документ до конца процесса.
Кликните, чтобы запустить.
Код для тестов находится в файле [insert-bench.js](insert-bench.js).
[/online]
## Добавление множества узлов
Продолжим работать со вставкой узлов.
Рассмотрим случай, когда в документе *уже есть* большой список `UL`. И тут понадобилось срочно добавить еще 20 элементов `LI`.
Как это сделать?
Если новые элементы пришли в виде строки, то можно попробовать добавить их так:
```js
ul.innerHTML += "
1
2
...";
```
Но операция `+=` с `innerHTML` не работает с DOM. Она не прибавляет, а заменяет всё содержимое списка на дополненную строку. Это не только медленно, но все внешние ресурсы (картинки) будут загружены заново. Так лучше не делать.
А если нужно вставить в середину списка? Здесь `innerHTML` вообще не поможет.
Можно, конечно, вставить строку во временный DOM-элемент и перенести оттуда элементы, но есть и гораздо лучший вариант: метод `insertAdjacentHTML`!
## insertAdjacent*
Метод [insertAdjacentHTML](https://developer.mozilla.org/en/DOM/element.insertAdjacentHTML) позволяет вставлять произвольный HTML в любое место документа, в том числе *и между узлами*!
Он поддерживается всеми браузерами, кроме Firefox меньше версии 8, ну а там его можно эмулировать.
Синтаксис:
```js
elem.insertAdjacentHTML(where, html);
```
`html`
Строка HTML, которую нужно вставить
`where`
Куда по отношению к `elem` вставлять строку. Всего четыре варианта:
`beforeBegin` -- перед `elem`.
`afterBegin` -- внутрь `elem`, в самое начало.
`beforeEnd` -- внутрь `elem`, в конец.
`afterEnd` -- после `elem`.
Например, вставим пропущенные элементы списка *перед* `
5
`:
```html
1
2
5
```
Единственный недостаток этого метода -- он не работает в Firefox до версии 8. Но его можно легко добавить, используя [полифилл insertAdjacentHTML для Firefox](insertAdjacentHTML.js).
У этого метода есть "близнецы-братья", которые поддерживаются везде, кроме Firefox, но в него они добавляются тем же полифиллом:
[elem.insertAdjacentElement(where, newElem)](http://help.dottoro.com/ljbreokf.php) -- вставляет в произвольное место не строку HTML, а элемент `newElem`.
[elem.insertAdjacentText(where, text)](http://help.dottoro.com/ljrsluxu.php) -- создаёт текстовый узел из строки `text` и вставляет его в указанное место относительно `elem`.
Синтаксис этих методов, за исключением последнего параметра, полностью совпадает с `insertAdjacentHTML`. Вместе они образуют "универсальный швейцарский нож" для вставки чего угодно куда угодно.
## DocumentFragment
[warn header="Важно для старых браузеров"]
Оптимизация, о которой здесь идёт речь, важна в первую очередь для старых браузеров, включая IE9-. В современных браузерах эффект от нее, как правило, небольшой, а иногда может быть и отрицательным.
[/warn]
До этого мы говорили о вставке строки в DOM. А что делать в случае, когда надо в существующий `UL` вставить много *DOM-элементов*?
Можно вставлять их один за другим, вызовом `insertBefore/appendChild`, но при этом получится много операций с большим живым документом.
**Вставить пачку узлов единовременно поможет `DocumentFragment`. Это особенный *кросс-браузерный* DOM-объект, который похож на обычный DOM-узел, но им не является.**
Синтаксис для его создания:
```js
var fragment = document.createDocumentFragment();
```
В него можно добавлять другие узлы.
```js
fragment.appendChild(node);
```
Его можно клонировать:
```js
fragment.cloneNode(true); // клонирование с подэлементами
```
**У `DocumentFragment` нет обычных свойств DOM-узлов, таких как `innerHTML`, `tagName` и т.п. Это не узел.**
Его "Фишка" заключается в том, что когда `DocumentFragment` вставляется в DOM -- то он исчезает, а вместо него вставляются его дети. Это свойство является уникальной особенностью `DocumentFragment`.
Например, если добавить в него много `LI`, и потом вызвать `ul.appendChild(fragment)`, то фрагмент растворится, и в DOM вставятся именно `LI`, причём в том же порядке, в котором были во фрагменте.
Псевдокод:
```js
// хотим вставить в список UL много LI
// делаем вспомогательный DocumentFragment
var fragment = document.createDocumentFragment();
for (цикл по li) {
fragment.appendChild(list[i]); // вставить каждый LI в DocumentFragment
}
ul.appendChild(fragment); // вместо фрагмента вставятся элементы списка
```
В современных браузерах эффект от такой оптимизации может быть различным, а на небольших документах иногда и отрицательным.
Понять текущее положение вещей вы можете, запустив следующий [edit src="benchmark"]небольшой бенчмарк[/edit].
## append/prepend, before/after, replaceWith
Сравнительно недавно в [стандарте](https://dom.spec.whatwg.org/) появились методы, которые позволяют вставить что угодно и куда угодно.
Синтаксис:
`node.append(...nodes)` -- вставляет `nodes` в конец `node`,
`node.prepend(...nodes)` -- вставляет `nodes` в начало `node`,
`node.after(...nodes)` -- вставляет `nodes` после узла `node`,
`node.before(...nodes)` -- вставляет `nodes` перед узлом `node`,
`node.replaceWith(...nodes)` -- вставляет `nodes` вместо `node`.
Эти методы ничего не возвращают.
Во всех этих методах `nodes` -- DOM-узлы или строки, в любом сочетании и количестве. Причём строки вставляются именно как текстовые узлы, в отличие от `insertAdjacentHTML`.
Пример (с полифиллом):
```html
```
## Итого
Манипуляции, меняющие структуру DOM (вставка, удаление элементов), как правило, быстрее с отдельным маленьким узлом, чем с большим DOM, который находится в документе.
Конкретная разница зависит от внутренней реализации DOM в браузере.
Семейство методов для вставки HTML/элемента/текста в произвольное место документа:
`elem.insertAdjacentHTML(where, html)`
`elem.insertAdjacentElement(where, node)`
`elem.insertAdjacentText(where, text)`
Два последних метода не поддерживаются в Firefox, на момент написания текста, но есть небольшой полифилл [insertAdjacentFF.js](insertAdjacentFF.js), который добавляет их. Конечно, он нужен только для Firefox.
`DocumentFragment` позволяет минимизировать количество вставок в большой живой DOM. Эта оптимизация особо эффективна в старых браузерах, в новых эффект от неё меньше или наоборот отрицательный.
Элементы сначала вставляются в него, а потом -- он вставляется в DOM. При вставке `DocumentFragment` "растворяется", и вместо него вставляются содержащиеся в нём узлы.
`DocumentFragment`, в отличие от `insertAdjacent*`, работает с коллекцией DOM-узлов.
Современные методы, работают с любым количеством узлов и текста, желателен полифилл: