Оптимизировать код javascript, конечно, надо не везде. Обычно - в ускорении нуждаются
Основные узкие места - как правило, там, где javascript вызывается очень часто. Мы рассмотрим основные причины тормозов и то, как их преодолеть.
А чтобы все было очевидно и наглядно для любых браузеров - примеры можно тестировать тут же, онлайн.
Любое обращение к DOM - обычно тяжелее для браузера, чем обращение к переменной javascript. Изменение свойств, влияющих на отображение элемента: className, style, innerHTML и ряда других - особенно сложные операции.
Уменьшение их числа может ускорить скрипт.
Например, эта функция строит элементарный интерфейс:
function buildUI(parent) {
parent.innerHTML = ''
parent.innerHTML += buildTitle()
parent.innerHTML += buildBody()
parent.innerHTML += buildFooter()
}
Оптимизация по части доступа к innerHTML будет такая:
function buildUI2(parent) {
var elementText = ''
elementText += buildTitle()
elementText += buildBody()
elementText += buildFooter()
parent.innerHTML = elementText
}
Нажатие на кнопку запускает многократный запуск функции и замер общего времени. В разных браузерах разница во времени выполнения будет разной.
Рассмотрим функцию, которая проходит всех детей узла и ставит им свойства:
function testAttachClick(parent) {
var elements = parent.getElementsByTagName('div')
for(var i=0; i<elements.length; i++) {
elements[i].onclick = function() {
alert('click on '+this.number)
}
elements[i].number = i
}
}
Сколько в ней обращений к DOM ?
Правильный ответ - 4 обращения.
Первое - самое очевидное:
var elements = parent.getElementsByTagName('div')
Функция getElementsByTagName() возвращает специальный объект NodeList, который похож на массив: есть длина и нумерованы элементы, но на самом деле это динамический объект DOM.
Например, если один из элементов NodeList будет вдруг удален из документа, то он пропадет и из elements.
Поэтому следующие обращения - тоже работают с DOM, причем на каждой итерации цикла:
elements.length elements[i].onclick elements[i].number
По возможности оптимизируем их:
function testAttachClick2(parent) {
var elements = parent.getElementsByTagName('div')
var len = elements.length
var elem
for(var i=0; i<len; i++) {
elem = elements[i]
elem.onclick = function() {
alert('click on '+this.number)
}
elem.number = i
}
}
Такая оптимизация полезна и в случае, когда elements - обычный массив, но эффект от уменьшения обращений к DOM NodeList гораздо сильнее.
Рассмотрим заодно еще небольшую оптимизацию. Функция, которая назначается onclick внутри цикла - статическая. Вынесем ее вовне цикла:
function testAttachClick3(parent) {
var elements = parent.getElementsByTagName('div')
var len = elements.length
var elem
var handler = function() {
alert('click on '+this.number)
}
for(var i=0; i<len; i++) {
elem = elements[i]
elem.onclick = handler
elem.number = i
}
}
В этом тесте цикл пробегает по 30 элементам. Чем больше элементов - тем больше видна разница. Проверьте в разных браузерах.
В Internet Explorer конкатенация строк реализована не совсем корректно. Особенно это видно на длинных строках.
Тормоза возникают, например, когда длинная строка конструируется из множества мелких.
Например, из массива данных делается HTML-таблица:
function makeTable() {
var s = '<table><tr>'
for(var i=0; i<arrayData.length; i++) {
s += '<td>' + arrayData[i] + '</td>'
}
s+='</tr></table>'
return s
}
По-видимому, каждый раз при сложении строк:
Хотя правильно было бы просто приписывать вторую строку к первой. Тут есть некоторые сложности на низком уровне - в работе с памятью и т.п, но они вполне преодолимы.
В некоторых языках предусмотрены специальные классы для сложения многих строк в одну. Например, в Java это StringBuilder.
Соответствующий прием в javascript - сложить все куски в массив, а потом - получить длинную строку вызовом Array#join.
Так будет выглядить оптимизированный пример с таблицей:
function makeTable2() {
var buffer = []
for(var i=0; i<arrayData.length; i++) {
buffer.push(arrayData[i])
}
var s = '<table><tr><td>' + buffer.join('</td><td>') + '</td></tr></table>'
return s
}
В этом тесте таблица делается из 150 ячеек данных, т.е всего примерно 150 операций прибавления строки к создаваемой таблице.
Тормоза на строках отчетливо видны в Internet Explorer.
В тех браузерах, где проблем со строками нет, заполнение массива является лишней операцией и общее время, наоборот, увеличивается.
Тем не менее, этот способ оптимизации можно применять везде, т.к он уменьшает максимальное время выполнения (IE).
И, конечно, конструирование через строки работает быстрее создания таблицы через DOM, когда каждый элемент делается document.createElement(..).
Только вот таблицу надо делать целиком, т.к в Internet Explorer свойство innerHTML работает только на самом нижнем уровне таблицы: TD, TH и т.п, и не работает для TABLE, TBODY, TR...
В IE есть такая интересная штука как CSS-expressions.
Как правило они используются для обхода IEшных недостатков и багов в верстке. Например:
p {
max-width:800px;
width:expression(document.body.clientWidth > 800? "800px": "auto" );
}
Идея хорошая, спору нет, это работает. Но есть здесь и подводный камень.
CSS expressions вычисляются при каждом событии, включая движение мыши, скроллинг окна и т.п.
Например, посмотрите эту страничку в Internet Explorer - полоса чуть ниже должна быть частично закрашена. Каждые 10 вычислений CSS expression меняют ее ширину на 1.
Клик на полоске покажет, сколько всего раз вычислилось CSS expression.
Если CSS-expression достаточно сложное, например, включает вложенные Javascript-вызовы для определения размеров элементов, то передвижение курсора может стать "рваным", как и при сложном обработчике onmousemove.