Бывают задачи, когда необходимо в цикле выполнять большое количество итераций и при каждой такой итерации нужно создать объект или элемент на странице, задать css-стиль и т.д. Когда итераций немного, то проблема не существенная, а что если вам нужно создать сто, тысячу, миллион объектов?
// Антишаблон, не использовать!
for (var i = 0; i < 1000000; i ++) {
console.log(i);
}
В данном случае браузер просто перестанет реагировать и повиснет.
Основные правила при использовании циклов при больших итерациях:
- счет итераций должен быть в обратном порядке — от максимального значения до нуля. Это дает дополнительный прирост в скорости из-за того, что операция сравнения с числом 0 немного эффективнее, чем операция сравнения с длинной массива или любым другим числом, отличным от 0;
- если известна длина цикла, то ее необходимо сохранить в отдельную переменную, так как при каждой итерации обращаться к свойству length снижает производительность;
- если вы используете JSLint, то советую оператор ++ или — заменить на +=1 и -=1, соответственно;
- в javascript циклы for, while и do while абсолютно одинаковы по скорости производительности, что нельзя сказать о for in, поэтому не важно какой именно цикл вы используете и старайтесь избегать for in там, где это возможно.
// Антишаблон, не использовать!
var count = 1000000;
while (count -= 1) {
console.log(count);
}
Даже если нам заранее известно количество итераций в цикле и если мы запустим цикл с последнего элемента и пойдем в порядке убывания, — как показано в примере выше — то браузер достигнув цикла, заставит пользователя ждать(в лучшем случае). И хорошо, если script расположен перед закрывающим тегом body, а не в head. В противном случае javascript не даст загрузиться html документу, пока полностью не будет выполнен.
Решение этой проблемы состоит в следующем:
- разбиваем наш миллионный(в данном случае) цикл на несколько частей;
- создаем таймер, который будет запускать цикл с итерациями для каждой части, как только итерации одной части заканчиваются, то таймер запускается заново для следующей части.
var count = 1000000,
parts = 500, // разбиваем на 500 частей, то есть в каждой части у нас будет 2000 итераций(1000000 / 500 = 2000)
maxIterations = count / parts, // собственно итерации для каждой части
iteration = 0; // счетчик итераций
setTimeout(function createCycle() {
/*
Для разнообразия привел цикл for, но как писал уже выше, разницы между while и for по производительности нет;
так же цикл сделал по возрастанию, чтоб показать, что даже в таком случае производительность НАМНОГО больше,
чем в предыдущем примере
*/
for (var i = 0; i < maxIterations; i += 1) {
console.log(i);
}
iteration ++;
if (iteration < parts) {
/*
Так как число не маленькое(1000000), то таймер необходимо запускать с большим интервалом,
в противном случае он не успев обработать одну часть(parts) до конца(от 1 до 2000)
приступит к выполнению следующей
*/
setTimeout(createCycle, 1000);
}
}, 1000);
Если же вы разбили свой цикл на достаточно большое количество частей и в каждой части у вас вышло итераций немного, то смело ставьте интервал в 25мс(использование задержки менее 25мс в Internet Explorer может привести к блокированию браузера).
Хочу заметить, что скрипт работает асинхронно, то есть пока выполняется цикл, пользователю не нужно ждать окончания последней итерации.
Обработка больших функций
Бывает, когда функция выполняется достаточно долгое время, в таком случае эту функцию желательно разделить на несколько маленьких функций, которые будут выполняться последовательно. Все, что для этого необходимо — это добавить каждую функцию в массив.
function foo1() { console.log('foo1'); }
function foo2() { console.log('foo2'); }
function foo3() { console.log('foo3'); }
function createTasks() {
var tasks = [foo1, foo2, foo3];
setTimeout(function getTask() {
tasks.shift()(); // вырезаем из массива первую функцию и обрабатываем ее, после чего берем следующую и т.д.
if (tasks.length > 0) {
setTimeout(getTask, 25);
}
}, 25);
}
createTasks();
В заключение
Во всех вышеописанных примерах таймеры срабатывали последовательно друг за другом, в связи с этим влияние на производительность было минимальным. Но, как только на странице начинают срабатывать несколько таймеров одновременно — начинаются проблемы. Из-за однопоточной работы браузеров, таймеры начинают конкурировать друг с другом за время, необходимое для выполнения переданных им функций. Чтобы обойти эту проблему желательно создать единственный таймер многократного срабатывания, выполняющий множество операций при каждом срабатывании.
Автор: theNobody