Производительность DocumentFragment по сравнению с appendChild для непривязанного элемента?

#javascript #dom

#javascript #dom

Вопрос:

Если я создаю много элементов DOM с помощью Javascript, обеспечивает ли их добавление в DocumentFragment, а затем добавление фрагмента в документ, лучшую производительность, чем добавление их в непривязанный элемент, а затем добавление этого элемента в DOM? т.е.

 var el;
var i = 0;
var fragment = document.createDocumentFragment();

while (i < 2000) {
    el = document.createElement('li');
    el.innerText = 'This is my list item number '   i;
    fragment.appendChild(el);
    i  ; }

document.appendChild(fragment);
  

против.

 var el;
var i = 0;
var container = document.createElement('div');

while (i < 2000) {
    el = document.createElement('li');
    el.innerText = 'This is my list item number '   i;
    container.appendChild(el);
    i  ; }

document.appendChild(container);
  

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

Спасибо

Ответ №1:

Я только что измерил именно это:

 function f1(){
    console.time("f1")
    let e = document.createElement("div")
    for(let i=0; i < 100000; i  ){
        let a = document.createElement("a")
        e.appendChild(a)
    }
    console.timeEnd("f1")
}

function f2(){
    console.time("f2")
    let e = document.createDocumentFragment()
    for(let i=0; i < 100000; i  ){
        let a = document.createElement("a")
        e.appendChild(a)
    }
    console.timeEnd("f2")
}
  

documentfragment работает значительно быстрее:

 f1: 494.0458984375ms      
f2: 346.35009765625ms     
  

Это chrome, firefox похож.

Я думал, что это будет то же самое. Следующий вопрос заключается в том, откуда берутся накладные расходы.

Комментарии:

1. 2021: снова измерено. chrome: f1 = 140 мс, f2 = 130 мс. firefox: f1 = 90 мс f2 = 90 мс. да, firefoxies, отличная работа! похоже, что для создания непривязанных поддеревьев dom это не имеет серьезного значения.

2. код f2 не добавляет documentfragment в элемент, в итоге нам придется добавить фрагмент документа к элементу. Если мы это сделаем — f2 будет медленнее, я думаю, потому что будет другой процесс для извлечения дочерних элементов из фрагмента документа в элемент.

3. хороший момент, тест должен включать этот шаг, согласен.

Ответ №2:

Из первых принципов я бы предположил, что использование фактического элемента-оболочки должно быть таким же быстрым или даже незначительно быстрее, чем DocumentFragment . Ускорение было бы результатом меньшего количества работы, необходимой для уведомления наблюдателей мутаций, и добавления одного узла в дочерний список вместо распаковки фрагмента и перемещения всех его дочерних элементов в список. На самом деле это будет зависеть от базовой структуры данных. массив будет иметь более высокий штраф, чем веревочная структура.

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

Ну, на самом деле вы даже не указали, что используете браузер. Существуют и другие реализации DOM.

Ответ №3:

Этот JSPerf DocumentFragment значительно превосходит appendChild , хотя innerHTML и значительно превосходит оба:

https://jsperf.com/appendchild-vs-documentfragment-vs-innerhtml/24

Ответ №4:

Оба подхода, упомянутые в вопросе, должны иметь одинаковую производительность, поскольку в обоих случаях мы вставляем только один элемент / фрагмент. Но в обоих случаях будет создаваться разная структура DOM, поскольку в случае a container мы создаем фактическую оболочку элемента по сравнению с fragment тем, где в DOM не будет видимой оболочки. Оба подхода должны вызывать reflow здесь один, поскольку мы вставляем только один элемент / фрагмент.

Теперь давайте сравним производительность, когда мы хотим создать ту же структуру DOM. Здесь я бы предпочел использовать a fragment вместо добавления <li> непосредственно к целевому узлу, чтобы избежать множественного reflows .

Давайте создадим длинный список, как показано ниже, используя оба подхода:

 <div id="target">
  <li>This is my list item number 0</li>
  <li>This is my list item number 1</li>
  <li>This is my list item number 2</li>
  <!-- Thousands of more list items -->
</div>
  

Вот пример сценария, который я написал, чтобы протестировать то же самое с помощью performance API. Для меня добавление всех <li> с использованием одного fragment было немного быстрее, чем прямое добавление каждого <li> к целевому узлу.

 function measureElement(target, testElementCount, index) {
  performance.mark(`element_start_${index}`); // Start
  for (let i = 0; i < testElementCount; i  ) {
    let el = document.createElement('li');
    el.innerText = 'This is my list item number '   i;
    target.appendChild(el);
  }
  performance.mark(`element_end_${index}`); // End
  performance.measure("element_measure", `element_start_${index}`, `element_end_${index}`);
}

function measureFragment(target, testElementCount, index) {
  performance.mark(`fragment_start_${index}`); // Start
  const fragment = document.createDocumentFragment();
  for (let i = 0; i < testElementCount; i  ) {
    let el = document.createElement('li');
    el.innerText = 'This is my list item number '   i;
    fragment.appendChild(el);
  }
  target.appendChild(fragment);
  performance.mark(`fragment_end_${index}`); // End
  performance.measure("fragment_measure", `fragment_start_${index}`, `fragment_end_${index}`);
}

// Test Runner
function init() {
  const testCount = 10;
  const testElementCount = 100000;
  const target = document.querySelector("#target");
  let entries, total;

  console.log('------------------------- Measure Using Element -------------------------');
  for (let i = 0; i < testCount; i  ) {
    target.innerHTML = "";
    measureElement(target, testElementCount, i);
  }
  entries = performance.getEntriesByName("element_measure");
  total = 0;
  for (const entry of entries) {
    total  = entry.duration;
  }
  console.log(`Total Time for ${testCount} iteration with ${testElementCount} list items : ${total}`);
  console.log(`Average Time for ${testCount} iteration with ${testElementCount} list items : ${total/testCount}`);

  console.log('------------------------- Measure Using Fragment -------------------------');
  for (let i = 0; i < testCount; i  ) {
    target.innerHTML = "";
    measureFragment(target, testElementCount, i);
  }
  entries = performance.getEntriesByName("fragment_measure");
  total = 0;
  for (const entry of entries) {
    total  = entry.duration;
  }
  console.log(`Total Time for ${testCount} iteration with ${testElementCount} list items : ${total}`);
  console.log(`Average Time for ${testCount} iteration with ${testElementCount} list items : ${total/testCount}`);
}

init();  
 <div id="target"></div>  

 "------------------------- Measure Using Element -------------------------"
"Total Time for 10 iteration with 100000 list items : 2624.8000000044703"
"Average Time for 10 iteration with 100000 list items : 262.48000000044703"
"------------------------- Measure Using Fragment -------------------------"
"Total Time for 10 iteration with 100000 list items : 2526.7999999970198"
"Average Time for 10 iteration with 100000 list items : 252.67999999970198"