Перейти к основному содержимому

[Medium] 📄 Event Loop

1. Зачем JavaScript нужна асинхронность? Объясните callback и event loop

Зачем JavaScript нужно асинхронное поведение? Объясните callback и event loop.

JavaScript по своей природе однопоточный. Одна из его основных задач — манипулирование DOM браузера. Если бы несколько потоков одновременно модифицировали один и тот же узел, поведение стало бы очень сложным. Чтобы этого избежать, JavaScript работает в однопоточной модели.

Асинхронное поведение — это практическое решение поверх этой модели. Если операция должна ждать 2 секунды, браузер не может просто замереть. Он выполняет синхронные задачи первыми, а асинхронные задачи ставятся в очередь задач (task queue).

Синхронный контекст выполнения можно представить как стек вызовов (call stack). Браузер сначала выполняет задачи в стеке вызовов. Когда стек вызовов становится пустым, он извлекает ожидающие задачи из очереди задач в стек вызовов.

  1. Браузер проверяет, пуст ли стек вызовов => Нет => продолжает выполнять задачи из стека вызовов.
  2. Браузер проверяет, пуст ли стек вызовов => Да => проверяет очередь задач => если задачи есть, перемещает одну в стек вызовов.

Этот повторяющийся цикл и есть концепция event loop.

console.log(1);

// Эта асинхронная функция является callback
setTimeout(function () {
console.log(2);
}, 0);

console.log(3);

// Порядок вывода: 1 3 2

2. Почему setInterval не точен по времени?

Почему setInterval не точен по времени?

  1. JavaScript однопоточный (одна задача за раз, остальные ждут в очереди). Если callback setInterval выполняется дольше самого интервала, следующий callback задерживается. Пример: интервал 1 секунда, но один callback занимает 2 секунды. Следующий запуск уже опаздывает на 1 секунду. Это смещение накапливается со временем.

  2. Браузеры и среды выполнения также устанавливают ограничения. В большинстве основных браузеров (Chrome, Firefox, Safari) практический минимальный интервал обычно составляет около 4 мс. Поэтому даже если вы установите 1 мс, реально он будет срабатывать примерно каждые 4 мс.

  3. Высокая нагрузка на CPU или память может задерживать выполнение. Такие задачи, как обработка видео или изображений, часто вызывают задержки таймеров.

  4. В JavaScript есть сборка мусора. Если callback интервала создаёт много объектов, работа GC может привносить дополнительные задержки.

Альтернатива (Alternative)

requestAnimationFrame

Если вы используете setInterval для анимации, рассмотрите замену на requestAnimationFrame.

  • Синхронизация с перерисовкой: выполняется, когда браузер готов нарисовать следующий кадр. Это гораздо точнее, чем угадывание времени перерисовки с помощью setInterval или setTimeout.
  • Лучшая производительность: поскольку он синхронизирован с циклами перерисовки, он не будет выполняться, когда перерисовка не нужна (например, для фоновых вкладок), экономя вычисления.
  • Автоматическое регулирование: подстраивает частоту выполнения под условия устройства, обычно около 60 FPS.
  • Высокоточная метка времени: получает параметр DOMHighResTimeStamp (микросекундная точность), полезный для точного тайминга анимации.
Пример
let startPos = 0;

function moveElement(timestamp) {
// обновляем позицию DOM-элемента
startPos += 5;
document.getElementById(
'myElement'
).style.transform = `translateX(${startPos}px)`;

// Продолжаем анимацию, пока элемент не достигнет целевой позиции
if (startPos < 500) {
requestAnimationFrame(moveElement);
}
}

// запуск анимации
requestAnimationFrame(moveElement);

moveElement() обновляет позицию каждый кадр (обычно 60 FPS), пока не достигнет 500 пикселей. Обычно это даёт более плавную и естественную анимацию, чем setInterval.