Learning Book

Производительность циклов

Производительность циклов

Кэширование длины массива

Каждое обращение к arr.length может стать точкой доступа к свойству. Кэширование убирает это:

const arr = new Array(1_000_000).fill(1);

// Без кэширования — arr.length читается каждую итерацию
for (let i = 0; i < arr.length; i++) { ... }

// С кэшированием длины
const len = arr.length;
for (let i = 0; i < len; i++) { ... }
Современный V8 оптимизирует arr.length в цикле автоматически. Но явное кэширование — хорошая практика: сигнализирует читателю, что вы знаете об этом.

Цикл vs методы массива

Для большинства задач методы массива (map, filter, reduce) предпочтительнее цикла:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Цикл — императивно, много кода
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    doubled.push(numbers[i] * 2);
  }
}

// Методы — декларативно, читаемо
const doubled2 = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * 2);

Цикл предпочтителен когда:

  • Нужен ранний выход (break) — методы не поддерживают
  • Очень большой массив (10M+ элементов) — методы создают промежуточные массивы
  • Нужно мутировать элементы по индексу

Избегайте создания объектов в цикле

// Плохо — создаёт объект на каждой итерации
for (let i = 0; i < 1000; i++) {
  processItem({ index: i, value: arr[i] }); // новый {} каждый раз
}

// Лучше — объект создаётся один раз
const item = {};
for (let i = 0; i < arr.length; i++) {
  item.index = i;
  item.value = arr[i];
  processItem(item);
}

Async в циклах

// Последовательное выполнение — await в цикле
for (const id of userIds) {
  const user = await fetchUser(id); // ждём каждый запрос
  console.log(user.name);
}

// Параллельное выполнение — Promise.all
const users = await Promise.all(userIds.map(id => fetchUser(id)));
Если запросы независимы — Promise.all быстрее последовательного цикла. Если порядок важен или есть лимиты API — последовательный цикл.

Длинный синхронный цикл блокирует UI поток браузера. Если цикл занимает более 16ms — возникает “jank” (заметные задержки анимации).

Решения:

  1. Web Workers — выполнять тяжёлые вычисления в отдельном потоке
  2. Chunking — разбить работу на куски через setTimeout/requestAnimationFrame:
async function processLargeArray(arr) {
  const CHUNK_SIZE = 1000;
  for (let i = 0; i < arr.length; i += CHUNK_SIZE) {
    const chunk = arr.slice(i, i + CHUNK_SIZE);
    processChunk(chunk);
    await new Promise(resolve => setTimeout(resolve, 0)); // отдать управление UI
  }
}