Улучшение производительности в JavaScript с помощью таймера

от автора

Бывают задачи, когда необходимо в цикле выполнять большое количество итераций и при каждой такой итерации нужно создать объект или элемент на странице, задать css-стиль и т.д. Когда итераций немного, то проблема не существенная, а что если вам нужно создать сто, тысячу, миллион объектов?

// Антишаблон, не использовать! for (var i = 0; i < 1000000; i ++) {     console.log(i); } 


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

Основные правила при использовании циклов при больших итерациях:

  1. счет итераций должен быть в обратном порядке — от максимального значения до нуля. Это дает дополнительный прирост в скорости из-за того, что операция сравнения с числом 0 немного эффективнее, чем операция сравнения с длинной массива или любым другим числом, отличным от 0;
  2. если известна длина цикла, то ее необходимо сохранить в отдельную переменную, так как при каждой итерации обращаться к свойству length снижает производительность;
  3. если вы используете JSLint, то советую оператор ++ или — заменить на +=1 и -=1, соответственно;
  4. в javascript циклы for, while и do while абсолютно одинаковы по скорости производительности, что нельзя сказать о for in, поэтому не важно какой именно цикл вы используете и старайтесь избегать for in там, где это возможно.

// Антишаблон, не использовать! var count = 1000000;  while (count -= 1) {     console.log(count); } 

Даже если нам заранее известно количество итераций в цикле и если мы запустим цикл с последнего элемента и пойдем в порядке убывания, — как показано в примере выше — то браузер достигнув цикла, заставит пользователя ждать(в лучшем случае). И хорошо, если script расположен перед закрывающим тегом body, а не в head. В противном случае javascript не даст загрузиться html документу, пока полностью не будет выполнен.

Решение этой проблемы состоит в следующем:

  1. разбиваем наш миллионный(в данном случае) цикл на несколько частей;
  2. создаем таймер, который будет запускать цикл с итерациями для каждой части, как только итерации одной части заканчиваются, то таймер запускается заново для следующей части.

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(); 

В заключение


Во всех вышеописанных примерах таймеры срабатывали последовательно друг за другом, в связи с этим влияние на производительность было минимальным. Но, как только на странице начинают срабатывать несколько таймеров одновременно — начинаются проблемы. Из-за однопоточной работы браузеров, таймеры начинают конкурировать друг с другом за время, необходимое для выполнения переданных им функций. Чтобы обойти эту проблему желательно создать единственный таймер многократного срабатывания, выполняющий множество операций при каждом срабатывании.

ссылка на оригинал статьи http://habrahabr.ru/post/200138/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *