Опубликован: 16.02.2009 | Уровень: специалист | Доступ: платный
Лекция 7:

Оптимизация JavaScript

Быстрые итераторы, регулярные выражения и другие "вкусности"

В этом разделе собраны некоторые практические советы по производительности отдельных конструкций в JavaScript-движках в браузере.

Итераторы

Давайте рассмотрим, какой способ перебора элементов будет максимально быстрым в JavaScript. У нас есть несколько возможностей для реализации цикла, ниже приведен полный вариант кода для тестирования.

<!-- набор элементов для результатов тестирования -->
<p id="test1"></p>
<p id="test2"></p>
<p id="test3"></p>
<p id="test4"></p>
<p id="test5"></p>
<p id="test6"></p>

<script type="text/javascript">
// выбираем все элементы из DOM-дерева
var items = document.getElementsByTagName("*");
// кэшируем текущий размер DOM-дерева
var length = items.length;
// запоминаем текущий момент времени
var time = new Date().getTime();

// запускаем первый тест, обычный перебор элементов массива,
// запускается 10000 раз
for (var j=0; j<10000; j++) {
	for (var i=0; i<items.length; i++) {
		var item = items[i];
	}
}
// выводим результат в подготовленный выше контейнер
document.getElementById('test1').innerHTML = 
	"Простой цикл: " + (new Date().getTime() - time);

time = new Date().getTime();

// кэшируем размер массива
for (var j=0; j<10000; j++) {

	for (var i=0; i<length; i++) {

		var item = items[i];

	}

}
document.getElementById('test2').innerHTML =
	"Простой цикл (с кэшированием): " + (new Date().getTime() - time);

time = new Date().getTime();

// встроенный for-in итератор для объекта массива
for (var j=0; j<10000; j++) {
	for (var i in items) {
		var item = items[i];
	}
}
document.getElementById('test3').innerHTML =
	"Простой через for-in: " + (new Date().getTime() - time);

time = new Date().getTime();

// обратный перебор элементов массива
for (var j=0; j<10000; j++) {
	for (var i = length - 1; i >= 0; i--) {
		var item = items[i];
	}
}
document.getElementById('test4').innerHTML =
	"Обратный: " + (new Date().getTime() - time);

time = new Date().getTime();

// итератор do-while
for (var j=0; j<10000; j++) {
	var i = 0;
	do {
		var item = items[i];
		i++;
	} while (i < length)
}
document.getElementById('test5').innerHTML =
	"do-while: " + (new Date().getTime() - time);


time = new Date().getTime();

// обратный while (самый быстрый)
for (var j=0; j<10000; j++) {
	var i = length - 1;
	while (--i) {
		var item = items[i];
	}
}
document.getElementById('test6').innerHTML =
	"Обратный while: " + (new Date().getTime() - time);
</script>

В результате мы получим примерно следующую таблицу:

Таблица 7.3. Различные варианты перебора массива (результаты в миллисекундах)
Браузер Обычный С КЭШем for-in Обратный do-while Обратный while
Firefox 3.0.3 714 657 835 280 297 217
Safari 3.1.2 141 140 157 125 125 93
Opera 9.61 188 125 765 94 94 78
IE 6 1281 1219 1094 468 500 360
IE 7 1391 1297 1250 515 532 406
IE 8b2 954 906 922 406 422 328
Chrome 0.2 288 246 332 117 114 95

В общем случае применение обратного while для перебора цикла в 2-3 раза быстрее всех остальных вариантов. Если веб-приложение оперирует массивами порядка 1000 элементов, то в результате применения оптимизированных приемов будет заметен значительный прирост производительности.

Регулярные выражения

В JavaScript есть несколько способов проверить, удовлетворяет ли строка заданному шаблону:

// 1. Объявляем объект в виде регулярного выражения
var RegExp = '/script/gi';
// и ищем в элементе массива совпадение с заданным шаблоном
items[i].nodeName.search(RegExp);

// 2. можно просто проверять соответствие строке,
// а не искать индекс подстроки
items[i].nodeName.match(RegExp);

// 3. Можно обойтись без объявления самого регулярного выражения
items[i].nodeName.match(/script/gi);

// 4. Можно задавать регулярное выражение без глобального модификатора,
// ведь мы ищем любое (=первое) совпадение шаблона
items[i].nodeName.match(/script/i);

// 5. С тем же успехом мы можем выполнить шаблон
/script/i.exec(items[i].nodeName);

// 6. Наконец, можно протестировать сам шаблон на нахождение в строке
/script/i.test(items[i].nodeName);

Давайте рассмотрим, что из них работает быстрее всего. Для этого запустим немного модифицированный набор тестов из раздела выше (опять по 10000 раз для всего DOM-дерева). Получим следующие результаты:

Таблица 7.4. Различные варианты перебора массива (результаты в миллисекундах)
Браузер search match "На лету" Локальный exec test
Firefox 3.0.3 2120 2041 1295 1273 1225 1348
Safari 3.1.2 453 469 344 359 360 359
Opera 9.61 2141 2063 406 344 312 313
IE 6 2594 2516 1875 1859 1953 1906
IE 7 2562 2469 1859 1844 2000 1860
IE 8b2 2140 2032 1453 1453 1547 1469
Chrome 0.2 856 870 416 397 385 392

Как мы видим, в данном случае создание нового регулярного выражения - весьма ресурсоемкий процесс, поэтому в большинстве случаев лучше обходиться без него. В остальном все браузеры ведут себя достаточно ровно при сопоставлении match, exec и test.

Мария Кравцова
Мария Кравцова
Россия, Сочи, РГПУ им. А.И.Герцена, 1997
Екатерина Архангельская
Екатерина Архангельская
Россия, СПбГУАП