JavaScript: лучше ли использовать вызовы innerHTML или (много) createElement для добавления сложной структуры div?
Я ищу проблему, которая требует сложного блока divs, который будет создан один раз для каждого элемента в наборе из ~ 100 элементов.
Каждый отдельный элемент идентичен, за исключением содержимого, и они выглядят (в HTML) примерно так:
<div class="class0 class1 class3">
<div class="spacer"></div>
<div id="content">content</div>
<div class="spacer"></div>
<div id="content2">content2</div>
<div class="class4">content3</div>
<div class="spacer"></div>
<div id="footer">content3</div>
</div>
Я мог бы:
1) Создайте все элементы как innerHTML
с конкатенацией строки, чтобы добавить контент.
2) Используйте createElement
, setAttribute
и appendChild
для создания и добавления каждого div.
Вариант 1 получает немного меньший файл для загрузки, но вариант 2 кажется немного более быстрым для рендеринга.
Помимо производительности, есть ли веская причина идти по одному маршруту или другому? Любые проблемы с перекрестным браузером/производительность gremlins, на которые я должен проверить?
... или я должен попробовать шаблон и клон?
Большое спасибо.
Ответы
Ответ 1
Ни. Используйте библиотеку, например jQuery, Prototype, Dojo или mooTools, потому что оба этих метода чреваты неприятностями:
Авторы основных библиотек javascript потратили много времени и имеют целые системы отслеживания ошибок, чтобы убедиться, что когда вы вызываете их инструменты изменения DOM, они действительно работают.
Если вы пишете библиотеку, чтобы конкурировать с вышеуказанными инструментами (и удачи вам, если вы есть), тогда я бы выбрал метод, основанный на производительности, а innerHTML всегда выигрывал в прошлом, и поскольку innerHTML является нативным методом, безопасная ставка будет оставаться самой быстрой.
Ответ 2
Зависит от того, что "лучше" для вас.
Производительность
С точки зрения производительности, createElement + appendChild выигрывает LOT. Взгляните на этот jsPerf, который я создал, когда сравниваю оба и результаты, попавшие в лицо.
innerHTML: ~120 ops/sec
createElement+appendChild: ~145000 ops/sec
(на моем Mac с Chrome 21)
Кроме того, innerHTML запускает переполнение страницы.
В Ubuntu с Chrome 39 тесты получают похожие результаты
innerHTML: 120000 ops/sec
createElement: 124000 ops/sec
возможно, какая-то оптимизация.
На Ubuntu с QtWebkit на основе браузера Arora (wkhtml также QtWebkit) результаты
innerHTML: 71000 ops/sec
createElement: 282000 ops/sec
кажется, что createElement быстрее в среднем
Mantainability
С точки зрения удобства, я считаю, что строковые шаблоны вам очень помогают. Я использую Handlebars (что мне нравится) или Tim (для проекта, требующего наименьших отпечатков). Когда вы "скомпилируете" (подготовьте) свой шаблон и готовы к его добавлению в DOM, вы используете innerHTML, чтобы добавить строку шаблона в DOM. В фокусе, который я делаю, чтобы избежать reflow, это createElement для обертки и в этом элементе-оболочке, поставьте шаблон с помощью innerHTML. Я все еще ищу хороший способ избежать innerHTML вообще.
Совместимость
Здесь вам не о чем беспокоиться, оба метода полностью поддерживаются широким спектром браузеров (в отличие от altCognito). Вы можете проверить графики совместимости для createElement и appendChild.
Ответ 3
altCognito делает хороший момент - использование библиотеки - путь. Но если бы это делалось вручную, я бы использовал опцию №2 - создать элементы с помощью методов DOM. Они немного уродливы, но вы можете создать элемент factory, который скрывает уродство. Конкатенация строк HTML также уродлива, но чаще возникает проблема безопасности, особенно с XSS.
Я бы определенно не добавлял новые узлы по отдельности. Я бы использовал DOM DocumentFragment. Добавление узлов в documentFragment происходит намного быстрее, чем вставка их в живую страницу. Когда вы закончите создание своего фрагмента, он просто будет вставлен сразу.
Джон Ресиг объясняет это намного лучше, чем я мог, но в основном вы просто говорите:
var frag = document.createDocumentFragment();
frag.appendChild(myFirstNewElement);
frag.appendChild(mySecondNewElement);
...etc.
document.getElementById('insert_here').appendChild(frag);
Ответ 4
Лично я использую innerHTML, потому что это то, к чему я привык, и для чего-то вроде этого, методы W3C добавляют в код много помех.
Можно ли просто сократить число div, есть ли какие-либо причины, по которым вы используете элементы spacer, вместо того, чтобы просто редактировать поля в div div?
Ответ 5
Поскольку вы упомянули шаблон и клон, вас может заинтересовать этот вопрос:
Другим вариантом является использование обертки DOM, например DOMBuilder:
DOMBuilder.apply(window);
DIV({"class": "class0 class1 class3"},
DIV({"class": "spacer"}),
DIV({id: "content"}, "content"),
DIV({"class": "spacer"}),
DIV({id: "content2"}, "content2"),
DIV({"class": "class4"}, "content3"),
DIV({"class": "spacer"}),
DIV({id: "footer"}, "content3")
);
Лично, если каждый элемент понадобится для создания той же самой структуры, я бы пошел с клонированием. Если какая-либо логика связана с созданием структуры, в которую будет идти контент, я бы предпочел сохранить что-то вроде выше, чем перебирать строки. Если бы этот подход оказался слишком медленным, я бы вернулся к innerHTML.
Ответ 6
Ни. Используйте cloneNode.
var div = document.createElement('div');
var millionDivs = [div];
while (millionDivs.length < 1e6) millionDivs.push(div.cloneNode())
Ответ 7
Я не думаю, что там можно выбирать между ними. В прежние дни (IE6, FF1.5), innerHTML был быстрее (benchmark), но теперь не получается чтобы быть заметной разницей в большинстве случаев.
В соответствии с mozilla dev. docs существует несколько ситуаций, когда поведение innerHTML варьируется между браузерами (особенно внутри таблиц), поэтому createElement даст вам больше согласованности - но innerHTML обычно меньше вводить.
Ответ 8
Как я знаю, самый быстрый способ - избегать редактирования DOM, если это возможно. Это означает, что лучше создать большую строку, а затем поместить ее в innerHTML. Но есть замечание для этого: не делайте слишком много операций над большими строками, быстрее использовать массив строк, а затем присоединить их все.
Ответ 9
Для такой сложной задачи я обычно использую методы innerHTML, потому что они: a) легче читать и модифицировать по коду; b) более удобно использовать в циклах. Как говорится в главном сообщении, они, к сожалению, терпят неудачу в IE (6,7,8) на элементах <table>
, <thead>
, <tbody>
, <tr>
, <tfoot>
, <select>
, <pre>
.
Ответ 10
1) Создайте все элементы как innerHTML с конкатенацией строк, чтобы добавить контент.
2) Используйте createElement, setAttribute и appendChild для создания и добавления каждого div.
3) компромисс. Создайте все элементы за один раз в виде innerHTML (что позволяет избежать небольшого количества манипулирования списком дочерних элементов), а затем записывайте содержимое, которое изменяется на каждом элементе, используя доступ к данным/атрибуту (избегая всех этих неприятных ошибок при использовании HTML-escape содержание). например. что-то вроде:
var html= (
'<div class="item">'+
'<div class="title">x</div>'+
'<div class="description">x</div>'+
'</div>'
);
var items= [
{'id': 1, 'title': 'Potato', 'description': 'A potato'},
{'id': 2, 'title': 'Kartoffel', 'description': 'German potato'},
// ... 100 other items ...
];
function multiplyString(s, n) {
return new Array(n+1).join(s);
}
var parent= document.getElementById('itemcontainer');
parent.innerHTML= multiplyString(html, items.length);
for (var i= 0; i<items.length; i++) {
var item= items[i];
var node= parent.childNodes[i];
node.id= 'item'+item.id;
node.childNodes[0].firstChild.data= item.title;
node.childNodes[1].firstChild.data= item.description;
}
Можно также комбинировать с подсказкой Neall о DocumentFragments.