Многие Веб-разработчики пишут горы кода на JavaScript, особенно в новом веке Web 2.0. Это мощная технология, но большинство браузеров содержат очень медленную реализацию движка, и многим в какой-то момент приходится пересматривать код и пытаться сделать его быстрее. В этой заметке я поделюсь своим опытом и покажу несколько трюков, которые помогут сделать Ваш код JavaScript таким быстрым, насколько это возможно.
Это первая статья из серии, будьте на связи.
Сценарий: Вы разрабатываете мощное приложение для Интернет, и Вам нужно динамически загрузить элементы, используя AJAX, добавив их в текущий документ. По какой-то причине Вы не хотите (или не можете) использовать полностью сформированный HTML, и получаете данные в массив JavaScript.
Я знаю два классических способах выполнить такую задачу: создать элементы, используя метод document.createElement(), и склеить HTML в строку, присвоим ее свойству parentElement.innerHTML. Конечно, Вы можете комбинировать оба способа. Рассмотрим эти подходы более детально.
Классический способ (и в идеальном мире – лучший) – использовать DOM для манипуляций над элементами:
1 2 3 4 5 | 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?
1 2 3 4 | 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.
1 2 3 4 | el.innerHTML = ''; for (var i = 1; i <= 1000; i++) { el.innerHTML += '<li>Element ' + i + '</li>'; } |
Вау, я сильно удивлен, насколько медленной может быть процедура добавления элементов (11391 – 307938 мс)! Забавный результат, не правда ли? А все оттого, что браузеры пытаются отрисовать список после каждого обновления, и это сильно замедляет работу. Небольшая оптимизация:
1 2 3 4 5 | var html = ''; for (var i = 1; i <= 1000; i++) { html += '<li>Element ' + i + '</li>'; } el.innerHTML = html; |
Все браузеры показали отличный результат (31 – 109 мс), но Internet Explorer по-прежнему медленный – 10994 мс. Я нашел решение, которой работает очень быстро во всех браузерах: создать массив кусков HTML, и затем склеить его используя пустую строку в качестве разделителя:
1 2 3 4 5 6 7 | 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? Я добавил еще пару примеров, чтобы разъяснить ситуацию:
1 2 3 4 5 6 7 | 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; |
И второй пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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(''); |
Вот результаты в виде таблицы и диаграммы.
| No | Method | IE 6 | IE 7 | FF 1.5 | FF 2.0 | Opera 9 |
|---|---|---|---|---|---|---|
| 1 | createElement() | 1403 | 219 | 166 | 328 | 63 |
| 2 | createElement() full | 1134 | - | - | - | - |
| 3 | innerHTML | 39757 | 20781 | 41058 | 307938 | 11391 |
| 4 | innerHTML optimized | 10994 | 46 | 50 | 109 | 31 |
| 5 | innerHTML/join | 400 | 31 | 47 | 125 | 31 |
| 6 | innerHTML/optimized+ | 28934 | 109 | 84 | 172 | 62 |
| 7 | innerHTML/join+ | 950 | 78 | 110 | 189 | 62 |
Вы можете посмотреть тест и получить собственные результаты производительности здесь.
Выводы
- Всегда используйте методы DOM, чтобы Ваш код соответствовал стандартам. Этот подход имеет удовлетворительную производительность и работает во всех браузерах.
- Если Вам нужна самая высокая скорость, используйте подход join+innerHTML, который является самым быстрым в данном тесте.
- Никогда не добавляйте строки HTML к innerHTML (даже если Вам нужно добавить маааааааленький элементик).
- Opera - самый быстрый браузер в мире, но Internet Explorer 7 тоже довольно шустр, а вот Firefox 2.0 удивил своей низкой производительностью.
- Никогда не верьте фанатикам вроде меня, и замеряйте производительность разных подходов сами (но не переживайте, Microsoft не проплатили мне рекламу своего браузера).
Ссылки на другие части
- Часть 1: Добавление элементов DOM в документ
- Часть 2: Применение стилей к элементам
- Часть 3: Подписка на события
- Часть 4: Анонимные функции (будет опубликовано скоро)
- Часть 5: Подписка на события по требованию (будет опубликовано скоро)
- Часть 6: Отображение и скрытие элементов (будет опубликовано скоро)
- Часть 7: Перечисление элементов коллекции (будет опубликовано скоро)

Хотелось бы увидеть результаты тех же тестов, но с использованием прототайпа ;)
Вот кстати ссылочка (еле вспомнил где читал ) на советы по оптимизации JS с описаниями “почему”, про innerHTML там кстати тоже есть ;)
За ссылку спасибо, довольно занятная статья. По поводу prototype – имеется в виду Insertion? Если да, то там используются методы DOM + туча проверок всякообразных…
Ага. Вот как раз и интересно на сколько упадет скорость из-за этих проверок.
Очень интересная статья.
Очень часто по работе приходится выбирать тот или иной метод решения задач и теряешь на этом кучу времени.
С нетерпением буду ждать продолжения
Ухты!!! Никогда бы не подумал, что innerHTML самый медленный. Я то, по наивности, думал, что если просто, то и быстро.
Продолжаю читать и благодарю за пост.
Не совсем innerHTML. Медленная только его модификация через +=. Если взять присвоить строку — то это самый быстрый способ подменить элементы.
Полезная статейка, но к большому сожалению многие программисты делаю “как проще”.
P.S. Try/catch – конструкция о которой не стоит забывать если не уверен в кроссплатформенности твоего решения. Try/catch позволяет писать код устойчивый к ошибкам.
Естественно, try/catch никто не отменял :-) Но вот в данном конкретном случае есть кроссплатформенное решение, которое еще и быстро работает. Особенно, когда нужно выжать максимум из браузера.
[...] После прочтения статьи Дмитрия Штефлюка про скорость работы javascript в разных браузерах я очень удивился – мой любимый Firefox оказался чуть ли не самым медленным браузером (после IE 6.0). Решил проверить самостоятельно на его тестовом примере и заодно сравнить скорость альфа-версии Firefox 3.0 с второй версией. А заодно установил Opera 9.20, которая вот вышла буквально вчера, чтобы удостовериться в ее быстроте. [...]
Great article. Thank you very much.
thanx for the article! interesting – i never thought of an array and “push” to do this. btw “push” is not compatible with ie5.0 if you care about that browser.
but one important thing – i think your ie6 install must be totally f***** up. i realy could not reproduce your measurements…
on my machine for ie6 (in test-order):
188, 125, (too long ;-), 94, 78
to get a comparison, ff1.5 takes 150ms for the first test here… so?!
to use innerHTML was also the recommendation of PPK (Peter Paul Koch), after his testings way back.
hm, there is clearly something wrong.
Thanks for this article, it save my day!
Thanks again!
Ну на счет opera – самы быстрый браузер – я бы не согласился. Safari рулит, потестируй. Минимум в 2 раза быстрее всех.
I don’t quite understand what it has to do with JavaScript optimization? The only JavaScript optimization I see here is using array for string concat. The rest you were talking about is optimization of interaction with DOM ( and to tell you honestly nothing very new). Still although this method is more descriptive than using DOM methods for creation of JS, it is still ugly. In the end HTML markup should never be bundled inside the JS code, it’s already a bad practice – visualization should be separate from the logic. According to current state of things and MVC HTML is a part of the view, which is why you should not mess its creation with control (JS). So such things as HTML template should be defined outside the script. There are different ways of implementing such templating mechanism, some frameworks support such options.
You can say that you don’t have time for that and you fine with this solution. In case of small script/hack you will be right. But in any case this is far from unobtrusive JavaScript or good practise in case of larger development.
BTW rest of your articles are also only connected to HTML/DOM/CSS/JS combination and not JS itself. I think it’s incorrect to name your article this way. Aren’t there quite useful topics like optimization of lookups, state pattern for avoiding if statements, etc. that really concern JS coding practices?
можно заменить на
(ага, читаем Lecompte) — будет еще быстрее :)
Для IE7 метод innerHTML не прокатывает для вставки <option> в <select>. Обрезается первый открывающий тэг <option>, в результате ни один из элементов списка не отображается.
array[idx++]=
еще делают так, array[array.length]=
проверенно, работает быстрее чем .push
а склеивание строк с помощью отератора += тормазит не только в JS…