В этом руководстве вы узнаете о цикле событий в JavaScript и о том, как JavaScript реализует модель параллелизма на его основе. Рассмотрим понятное объяснение цикла событий в JavaScript.
Однопоточная модель
JavaScript — это однопоточный язык программирования. Это означает, что JavaScript может выполнять только одну операцию в один и тот же момент времени.
Движок JavaScript выполняет скрипт из верхней части файла и работает вниз. Он создает контексты выполнения, помещает и извлекает функции из стека вызовов на этапе выполнения.
Если функция выполняется долго, вы не можете взаимодействовать с веб-браузером во время выполнения функции, потому что страница зависает.
Функция, выполнение которой занимает много времени, называется блокирующей функцией. Технически функция блокировки блокирует все действия на веб-странице, например щелчок мышью.
Примером блокирующей функции является функция, которая вызывает API с удаленного сервера.
В следующем примере используется большой цикл для имитации блокирующей функции:
function task(message) { // emulate time consuming task let n = 10000000000; while (n > 0){ n--; } console.log(message); } console.log('Start script...'); task('Call an API'); console.log('Done!');
В этом примере у нас есть большой цикл while
внутри функции task()
, который эмулирует трудоемкую задачу. Функция task()
является блокирующей.
Скрипт зависает на несколько секунд(в зависимости от скорости компьютера) и выдает следующий вывод:
Start script... Download a file. Done!
Чтобы выполнить сценарий, механизм JavaScript помещает первый вызов console.log()
поверх стека вызовов и выполняет его. Затем он помещает функцию task()
поверх стека вызовов и выполняет функцию.
Однако для завершения функции task()
потребуется некоторое время. Поэтому вы увидите сообщение 'Download a file.'
немного времени спустя. После завершения функции task()
движок JavaScript извлекает ее из стека вызовов.
Наконец, движок JavaScript делает последний вызов функции console.log('Done!')
и выполняет ее, что будет очень быстро.
Обратные вызовы
Чтобы блокирующая функция не блокировала другие действия, вы обычно помещаете ее в функцию обратного вызова для последующего выполнения. Например:
console.log('Start script...'); setTimeout(() => { task('Download a file.'); }, 1000); console.log('Done!');
В этом примере вы увидите сообщение 'Start script...'
и 'Done!'
немедленно. И после этого вы увидите сообщение 'Download a file'
.
Вот результат:
Start script... Done! Download a file.
Как упоминалось ранее, движок JavaScript может делать только одну вещь за раз. Однако точнее будет сказать, что среда выполнения JavaScript может выполнять одну операцию за раз.
Веб-браузер также имеет другие компоненты, а не только механизм JavaScript.
Когда вы вызываете функцию setTimeout()
, отправляете запрос на выборку или нажимаете кнопку, веб-браузер может выполнять эти действия одновременно и асинхронно.
setTimeout()
, запросы на выборку и события DOM являются частью веб-API веб-браузера.
В нашем примере при вызове функции setTimeout()
движок JavaScript помещает ее в стек вызовов, а веб-API создает таймер, который истекает через 1 секунду.
Затем движок JavaScript помещает функцию task()
в очередь, называемую очередью обратного вызова или очередью задач:
Цикл событий — это постоянно работающий процесс, который отслеживает как очередь обратного вызова, так и стек вызовов.
Если стек вызовов не пуст, цикл событий ожидает, пока он не станет пустым, и помещает следующую функцию из очереди обратного вызова в стек вызовов. Если очередь обратного вызова пуста, ничего не произойдет:
См. другой пример:
console.log('Hi!'); setTimeout(() => { console.log('Execute immediately.'); }, 0); console.log('Bye!');
В этом примере время ожидания равно 0 секундам, поэтому появляется сообщение 'Execute immediately.'
должно появиться перед сообщением 'Bye!'
. Однако это так не работает.
Движок JavaScript помещает следующий вызов функции в очередь обратного вызова и выполняет его, когда стек вызовов пуст. Другими словами, движок JavaScript выполняет его после console.log('Bye!')
.
console.log('Execute immediately.');
Вот результат:
Hi! Bye! Execute immediately.
На следующем рисунке показана среда выполнения JavaScript, веб-API, стек вызовов и цикл обработки событий.