Польза от documentFragment

В свое время читал много статей о documentFragment, но долго не мог понять, что в нем реально крутого.

Через некоторое время осознал, что ничего, но кое-что есть. Об этом и поговорим

Что такое documentFragment ?

documentFragment - "фрагмент документа" наиболее близок по смыслу к обычному DOM-элементу.

То есть, его можно создать:

var fragment = document.createDocumentFragment()

в него можно добавлять другие узлы.

fragment.appendChild(node)

Фишка заключается в том, что когда documentFragment вставляется в DOM - то он исчезает, а вместо него вставляются его дети. Это единственное и основное свойство documentFragment по сравнению со всеми остальными сущностями DOM.

То есть, можно добавить в него много TD, и потом append к TR. При этом фрагмент растворится и вставятся именно TD как прямые потомки.

Когда и почему нужен documentFragment?

Возврат множества узлов из функции

Первый случай, когда documentFragment применим - это возврат множества узлов из функции. Можно это сделать возвращением массива, а можно вернуть documentFragment:

function makeRow() {
  var fragment = document.createDocumentFragment()
  var td_1 = document.createElement('TD')
  
  fragment.appendChild(td_1)
  ...
  fragment.appendChild(td_n)

  return fragment
}

Затем внешняя функция может вставить его в DOM или использовать далее без промежуточных массивов.

Быстрая вставка в DOM

Это, пожалуй, самый важный случай.

Допустим, нам нужно создать пачку элементов TR и вставить их в DOM.

Первый способ - создавать их и вставлять. И так n раз.

Но операция вставки в "живой" DOM дорогая. И тут на помощь приходит как раз documentFragment. Можно вставлять в него, а уже потом его добавить в DOM.

При этом скорость будет отличаться.

Совершенно не важно, используете ли вы в качестве оторванного от DOM контейнера documentFragment или что-то другое.

Важно, что промежуточные вставки идут в оторванный от живого документа DOM.

Например, вот два бенчмарка.

Оба они создают таблицу 10x10, наполняя TBODY элементами TR/TD.

При этом первый вставляет все в документ тут же, второй - задерживает вставку TBODY в документ до конца процесса.

Кликните, чтобы запустить.

/* appends elements right after creation */
appendFirst = 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')

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

/* appends elements right after creation
BUT wrapping tbody appended late */
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)
  }
  
}

При чем же здесь documentFragment? А не при чем! Вместо него может быть любой узел, но иногда удобен именно documentFragment из-за своего свойства растворяться при вставке.

То есть, когда неудобно делать промежуточный узел-контейнер, например при добавлении множества TR, подгруженных с сервера. Вместо n вызовов appendChild в живой DOM имеем только 1 вызов, это может дать экономию такую же как в бенчмарках выше.

Клонирование узлов

У documentFragment есть метод cloneNode, т.е. его можно клонировать вместе со всеми узлами.

Это также бывает удобно (1 вызов вместо n), но опять же - нет разницы, что клонировать - обычный узел вне DOM или documentFragment.

Удобство documentFragment лишь в том, что при вставке в документ он исчезнет. Как правило, это полезно в таблицах, гридах и т.п.

Резюме

Фрагмент документа ничего не оптимизирует сам по себе, он не быстрее обычного документа.

Оптимизация заключается именно в том, что все действия делаются вне DOM.

Но благодаря свойству растворяться при вставке, documentFragment уникален, и его стоит иметь в виду. Не всегда можно и удобно создавать настоящий контейнер, а такой, временный - может очень даже пригодиться.