Event Loop

Параллелизм в JavaScript основывается на модели "событийного цикла". Эта модель отличается от модели других языков, например C или Java.

Стек

Вызов любой функции создает контекст выполнения. При вызове вложенной функции создается новый контекст, а старый сохраняется в специальной структуре. Так формируется стек контекстов.

function f(b) {
  var a = 12;
  return a + b + 35;
}

function g(x) {
  var m = 4;
  return f(m * x);
}

g(21);

Когда вызываетсяg, создаётся область видимости, содержащая аргументыgи локальные переменные. Когдаgвызываетf, создается вторая область видимости и помещается в стек вперед первой, которая содержит аргументыfи ее локальные переменные. Когдаfвозвращает результат, верхний элемент из стека удаляется. Когдаgвозвращает результат, ее контекст также удалится, и стек будет пуст.

Куча

Объекты размещаются в куче. Куча — это ссылка на определенную неструктурированную область памяти.

Очередь

Среда выполнения JavaScript содержит очередь событий. Очередь событий — это список событий, подлежащих обработке. Каждое событие ассоциируется с некоторой функцией. Когда на стеке освобождается достаточно места, событие извлекается из очереди и обрабатывается. Обработка события состоит в вызове этой функции (и таким образом создании начального контекста выполнения). Обработка события заканчивается, когда стек снова становится пустым.

Цикл событий

Модель событийного цикла (event loop) называется так потому, что отслеживает новые события в цикле:

while(queue.waitForMessage()){
  queue.processNextMessage();
}

queue.waitForMessageожидает события, если очередь пуста.

Запуск до завершения

Каждое событие должно быть полностью обработано прежде, чем начнет обрабатываться следующее. Благодаря этому мы можем точно знать: когда бы и где бы ни выполнялась функция – она не может быть приостановлена и будет целиком завершена до начала выполнения другого кода (который может изменять данные, с которыми работает текущая функция). Это отличает JavaScript от такого языка программирования, как C. Поскольку в С функция, запущенная в отдельном потоке, в любой момент может быть остановлена, чтобы выполнить какой-то другой код в другом потоке.

У данного подхода есть и минусы. Если событие обрабатывается слишком долго, то приложение в это время не имеет возможности обработать действия пользователя (например, скролл или клик). Браузер может смягчать последствия такой проблемы. Internet Explorer в данном случае выводит сообщение "A script on this page is causing Internet Explorer to run slowly" и предлагает завершить обработку слишком тяжелого события. Хорошая практика для предотвращения этого – не создавать события, которые могут выполняться долго и разбивать большие события на несколько мелких.

Добавление событий в очередь

В браузерах события добавляются в очередь в любое время, если событие произошло, а так же если у него есть обработчик. В случае, если обработчика нет – событие потеряно. Так, клик по элементу, имеющему обработчик события по событиюclick, добавит событие в очередь, а если обработчика нет – то и событие в очередь не попадет.

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

Нулевые задержки

Нулевая задержка не дает гарантии, что обработчик выполнится через ноль миллисекунд. Вызов setTimeoutс аргументом 0 (ноль) не завершится за указанное время. Выполнение зависит от количества ожидающих задач в очереди. Например, сообщение ''this is just a message'' из примера ниже будет выведено на консоль раньше, чем произойдет выполнение обработчика сb1. Это произойдет, потому что задержка – это минимальное время, которое требуется среде выполнения на обработку запроса.

(function () {

  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the end');

})();

// "this is the start"
// "this is just a message"
// "this is the end"
// "this is a msg from call back"
// "this is a msg from call back1"

Связь нескольких потоков между собой

Web Worker или кросс-доменный фрейм имеют свой собственный стек, кучу и очередь событий. Два отдельных событийных потока могут связываться друг с другом только через отправку сообщений с помощью метода postMessage.Этот метод добавляетсообщение в очередь другого, если он конечно принимает их.

Никогда не блокируется

Очень интересное свойство цикла событий в JavaScript, что, в отличие от множества других языков, поток выполнения никогда не блокируется. Обработка I/O обычно осуществляется с помощью событий и функций обратного вызова, поэтому даже когда приложение ожидает запрос от IndexedDB или ответ от XHR, оно может обрабатывать другие процессы, например: пользовательский ввод.

Существуют хорошо известные исключения как alert или синхронный XHR, но считается хорошей практикой избегать их использования.

Last updated