Оптимизация JavaScript часть 1: Добавление элементов DOM в документ

Многие Веб-разработчики пишут горы кода на JavaScript, особенно в новом веке Web 2.0. Это мощная технология, но большинство браузеров содержат очень медленную реализацию движка, и многим в какой-то момент приходится пересматривать код и пытаться сделать его быстрее. В этой заметке я поделюсь своим опытом и покажу несколько трюков, которые помогут сделать Ваш код JavaScript таким быстрым, насколько это возможно.

Это первая статья из серии, будьте на связи.


Сценарий: Вы разрабатываете мощное приложение для Интернет, и Вам нужно динамически загрузить элементы, используя AJAX, добавив их в текущий документ. По какой-то причине Вы не хотите (или не можете) использовать полностью сформированный HTML, и получаете данные в массив JavaScript.

Я знаю два классических способах выполнить такую задачу: создать элементы, используя метод document.createElement(), и склеить HTML в строку, присвоим ее свойству parentElement.innerHTML. Конечно, Вы можете комбинировать оба способа. Рассмотрим эти подходы более детально.

Классический способ (и в идеальном мире — лучший) — использовать DOM для манипуляций над элементами:

for (var i = 1; i <= 1000; i++) {

    var li = document.createElement('li')
    li.appendChild(document.createTextNode('Element ' + i));

    el.appendChild(li);
}

Не такая уж и плохая производительность. Internet Explorer 6 самый медленный — 1403 мс (но ведь это самый медленный браузер в мире, правда?), остальные же браузеры справились довольно шустро (63 – 328 мс). Ладно, но как насчет создания элемента DOM прямо из кода HTML?

for (var i = 1; i <= 1000; i++) {
    var li = document.createElement('<li>Element ' + i + '</li>');

    el.appendChild(li);
}

Работает значительно лучше в Internet Explorer 6 (1134 мс), но вообще не работает в других браузерах. Блин! Конечно, Вы можете добавить блок try/catch и создать элементы, используя первый подход в блоке catch для остальных браузеров. Но у меня есть решение получше.

Every DOM node has attribute innerHTML which holds all child nodes as HTML string.

el.innerHTML = '';
for (var i = 1; i <= 1000; i++) {

    el.innerHTML += '<li>Element ' + i + '</li>';
}

Вау, я сильно удивлен, насколько медленной может быть процедура добавления элементов (11391 – 307938 мс)! Забавный результат, не правда ли? А все оттого, что браузеры пытаются отрисовать список после каждого обновления, и это сильно замедляет работу. Небольшая оптимизация:

var html = '';
for (var i = 1; i <= 1000; i++) {

    html += '<li>Element ' + i + '</li>';
}
el.innerHTML = html;

Все браузеры показали отличный результат (31 – 109 мс), но Internet Explorer по-прежнему медленный — 10994 мс. Я нашел решение, которой работает очень быстро во всех браузерах: создать массив кусков HTML, и затем склеить его используя пустую строку в качестве разделителя:

var html = [];
for (var i = 1; i <= 1000; i++) {

    html.push('<li>Element ');
    html.push(i);    

    html.push('</li>');
}
el.innerHTML = html.join('');

Это самый быстрый подход для Internet Explorer 6 400 мс, и довольно быстрый для остальных браузеров (31 – 125 ms). Почему я не говорю самый быстрый в случае с Firefox? Я добавил еще пару примеров, чтобы разъяснить ситуацию:

var html = '';
for (var i = 1; i <= 1000; i++) {

    html += '<li style="padding-left: ' + (i % 50) + 
    '" id="item-' + i + '">Element ' + i + ' Column ' + 

    (i % 50) + '</li>';
}
el.innerHTML = html;

И второй пример:

var html = [];
for (var i = 1; i <= 1000; i++) {

    html.push('<li style="padding-left: ');
    html.push(i % 50);

    html.push('" id="item-');
    html.push(i);

    html.push('">Element ');
    html.push(i);

    html.push(' Column ');
    html.push(i % 50);

    html.push('</li>');
}
el.innerHTML = html.join('');

Вот результаты в виде таблицы и диаграммы.

NoMethodIE 6IE 7FF 1.5FF 2.0Opera 9
1createElement()140321916632863
2createElement() full1134----
3innerHTML39757207814105830793811391
4innerHTML optimized10994465010931
5innerHTML/join400314712531
 
6innerHTML/optimized+289341098417262
7innerHTML/join+9507811018962

Рисунок 1. Тест производительности: Добавление элементов DOM в документ

Вы можете посмотреть тест и получить собственные результаты производительности здесь.

Выводы

  • Всегда используйте методы DOM, чтобы Ваш код соответствовал стандартам. Этот подход имеет удовлетворительную производительность и работает во всех браузерах.
  • Если Вам нужна самая высокая скорость, используйте подход join+innerHTML, который является самым быстрым в данном тесте.
  • Никогда не добавляйте строки HTML к innerHTML (даже если Вам нужно добавить маааааааленький элементик).
  • Opera — самый быстрый браузер в мире, но Internet Explorer 7 тоже довольно шустр, а вот Firefox 2.0 удивил своей низкой производительностью.
  • Никогда не верьте фанатикам вроде меня, и замеряйте производительность разных подходов сами (но не переживайте, Microsoft не проплатили мне рекламу своего браузера).
  • примечание от sunnybear: замена html.push на html[idx++] в последнем варианте (по Lecompte) позволяют выиграть до 50% в производительности (зафисировано для IE7)

Читать дальше

Все комментарии