Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

Мультивставка: "insertAdjacentHTML" и "DocumentFragment"

  1. Оптимизация вставки в документ
  2. Добавление множества узлов
  3. DocumentFragment
  4. insertAdjacentHTML
  5. Итого

Обычные методы вставки работают с одним узлом. Но есть и способы вставлять множество узлов одновременно.

Оптимизация вставки в документ

Нужно сгенерировать список UL/LI.

Какой алгоритм работает быстрее?

  1. Сгенерировать UL. Вставить в документ. Добавить к нему LI.
  2. Сгенерировать 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 вставлять строку. Всего четыре варианта:
  1. beforeBegin — перед elem.
  2. afterBegin — внутрь elem, в самое начало.
  3. beforeEnd — внутрь elem, в конец.
  4. afterEnd — после elem.

Например, вставим пропущенные элементы списка перед <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.

Итого

  • Вставка в документ всегда медленнее, чем вставка в узел вне документа. Чем больше размер DOM документа, тем медленнее в него вставка, из-за внутренних структур и зависимостей.
  • Метод elem.insertAdjacentHTML(where, html) поддерживается всеми современными браузерами.

    Он позволяет вставлять HTML-текст в произвольное место документа.

    Совместимость:

    • В Firefox<8 его нет.
    • IE6-7 поддерживают его лишь для техэлементов, где работает свойство innerHTML (т.е. кроме TABLE, TR и т.п.).
  • DocumentFragment позволяет минимизировать количество вставок в живой документ. Этот объект похож на обычный DOM-узел, но при вставке «растворяется», и вместо него вставляются содержащиеся в нём узлы.

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>

Подсказки
Решение
Подсказки
  • Проверить поддержку insertAdjacentHTML можно так:
    if (elem.insertAdjacentHTML) { ... }
    
  • Если этот метод не поддерживается, то сделайте временный элемент, через innerHTML поставьте туда html, а затем переместите содержимое в DocumentFragment. Последнее действие — вставка в документ.
Решение
Решение

<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>

См. также:

Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!