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