предисловие
Philip Robertsв речиgreat talk at JSConf on the event loopГоворит: Если бы мне пришлось описать JavaScript в одном предложении, я бы сказал: «JavaScript — это однопоточный, асинхронный, неблокирующий, интерпретируемый язык сценариев».
Один поток?Асинхронный??Неблокирующий???
Я всегда был искателем знаний с позицией «не проси большего», но я все еще чувствую тревогу невежества. Итак, за последние несколько дней я ходил по горам и горам, чтобы прочитать много информации. Чем скучнее документация, тем больнее она для меня. Но я также, кажется, научился некоторым вещам, поэтому я также записал несколько многословных вещей, которые я использовал в качестве обзора.
однопоточный против многопоточного
Однопоточный язык: JavaScript предназначен для обработки взаимодействия веб-страниц браузера (обработка операций DOM, анимация пользовательского интерфейса и т. д.), что определяет, что это однопоточный язык.
Если над DOM одновременно работает несколько потоков, на веб-странице будет беспорядок.
JavaScript является однопоточным, поэтому задачи обработки обрабатываются одна за другой и выполняются последовательно сверху вниз:
console.log('script start')
console.log('do something...')
console.log('script end')
// script start
// do something...
// script end
Приведенный выше код будет печатать последовательно: "начало сценария" >> "сделать что-нибудь..." >> "конец сценария"
Если обработка задачи занимает много времени (или ждет), например: сетевые запросы, таймеры, ожидание кликов мыши и т. д., последующие задачи также будут заблокированы, а это означает, что все действия пользователя (кнопки, полосы прокрутки и т. д.), это принесет очень недружественный опыт.
но:
console.log('script start')
console.log('do something...')
setTimeout(() => {
console.log('timer over')
}, 1000)
// 点击页面
console.log('click page')
console.log('script end')
// script start
// do something...
// click page
// script end
// timer over
"timer over"
существует"script end"
Затем распечатайте его, что означает, что таймер не блокирует следующий код. Что случилось?
На самом деле, однопоточность JavaScript означает, что в браузере есть только один поток, отвечающий за интерпретацию и выполнение кода JavaScript, который являетсяПоток движка JS, но процесс рендеринга браузера обеспечивает несколько потоков, как показано ниже:
- Поток движка JS
- поток триггера события
- синхронизированный триггерный поток
- Асинхронный поток HTTP-запросов
- Поток рендеринга графического интерфейса
Справочник по процессу рендеринга в браузерездесь
При обнаружении задач таймера, мониторинга событий DOM или сетевых запросов механизм JS напрямую передает их вебапи, то есть соответствующему потоку, предоставленному браузером (например, таймеру времени потока setTimeout, потоку асинхронного HTTP-запроса, обрабатывающему сетевой запрос) для процесс, а поток JS-движка продолжает выполнять другие задачи, что достигаетсяАсинхронный неблокирующий.
Таймер запускает поток также только дляsetTimeout(..., 1000)
Сроки только, когда время истечет, соответствующая функция обратного вызова (обратный вызов) будет переданаочередь сообщенийДля поддержания поток JS-движка будет обращаться к очереди сообщений, чтобы получить сообщение и выполнить его в нужное время.
Когда поток движка JS обрабатывает его? Что такое очередь сообщений?
Здесь JavaScript проходитцикл событий event loopмеханизм решения этой проблемы.
Давай обсудим это позже!
Синхронный и асинхронный
Вышеупомянутая асинхронность, в JavaScript есть синхронный код и асинхронный код.
Вот синхронизация:
console.log('hello 0')
console.log('hello 1')
console.log('hello 2')
// hello 0
// hello 1
// hello 2
Они выполняются друг за другом, и после выполнения возвращается результат (результат печатается).
setTimeout(() => {
console.log('hello 0')
}, 1000)
console.log('hello 1')
// hello 1
// hello 0
надsetTimeout
Функция не возвращает результат сразу, а инициирует асинхронную функцию, setTimeout — асинхронная функция инициации или функция регистрации, а () => {...} — асинхронная функция обратного вызова.
Здесь поток движка JS заботится только о том, кто является асинхронной функцией инициатора и что такое функция обратного вызова? А асинхронность отдать на обработку вебапи, а потом продолжать выполнять другие задачи.
Асинхронность обычно следующая:
- сетевой запрос
- таймер
- Мониторинг времени DOM
- ...
Цикл событий и очередь сообщений
вернуться к циклу событий циклу событий
фактическицикл событиймеханизм иочередь сообщенийОбслуживание управляется потоком, запускаемым событием.
поток триггера событияТакже предоставляемый механизмом рендеринга браузера, он поддерживаеточередь сообщений.
Когда поток движка JS сталкивается с асинхронностью (мониторинг событий DOM, сетевой запрос, таймер setTimeout и т. д.), он будет передан соответствующему потоку для поддержки асинхронной задачи в одиночку, ожидая определенное время (таймер заканчивается, сеть запрос успешен, пользователь щелкает DOM), затемпоток триггера событиябудет асинхронным, соответствующимПерезвонитеДобавленная в очередь сообщений функция обратного вызова в очереди сообщений ожидает выполнения.
В то же время поток движка JS будет поддерживатьстек выполнения, синхронный код будет добавлен в стек выполнения и выполнен по очереди, а в конце выйдет из стека выполнения.
Если выполнение задач в стеке выполнения завершено, то есть когда стек выполнения пуст (то есть поток JS-движка простаивает), поток, запускаемый событием, выведет задачу (то есть асинхронный функцию обратного вызова) из очереди сообщений и поместить ее в стек выполнения для выполнения.
Очередь сообщений — это структура данных, похожая на очередь, которая следует правилу «первым пришел — первым обслужен» (FIFO).
После завершения выполнения стек выполнения снова пуст, поток, запускаемый событием, повторяет предыдущую операцию, а затем вынимает задачу из очереди сообщений.Этот механизм называется механизмом цикла событий.
Или код выше:
console.log('script start')
setTimeout(() => {
console.log('timer over')
}, 1000)
// 点击页面
console.log('click page')
console.log('script end')
// script start
// click page
// script end
// timer over
Процесс реализации:
-
Основной блок кода (скрипт) добавляется в стек выполнения по очереди и выполняется последовательно.Основной блок кода:
- console.log('script start')
- setTimeout()
- console.log('click page')
- console.log('script end')
-
console.log() — это синхронный код, обрабатываемый потоком движка JS, печатает «запуск сценария» и выталкивает стек;
-
Обнаружена асинхронная функция
setTimeout
и передать его потоку триггера таймера (асинхронная функция триггера:setTimeout
, функция обратного вызова:() => { ... }
), поток движка JS продолжается, извлекает стек; -
console.log() — это синхронный код, обрабатываемый потоком движка JS, печатает «страницу клика» и извлекает стек;
-
Console.log() обрабатывается для синхронного кода, потока JS-движка, печати «конец сценария», из стека;
-
Стек выполнения пуст, то есть поток JS-движка простаивает, в это время из очереди сообщений (если есть) берется задача (обратный вызов), добавляется в стек выполнения и выполняется;
-
Повторите шаг 6.
-
(Расположение этого шага неизвестно) В определенный момент (после 1000 мс) поток триггера таймера уведомляет поток триггера события, и поток триггера события вызывает функцию
() => { ... }
Присоединитесь к хвосту очереди сообщений и дождитесь выполнения потока JS-движка.
Видно, что функция обратного вызова, соответствующая асинхронной функции setTimeout (() => {}
) будет выполняться после того, как стек выполнения станет пустым и будет выполнен основной блок кода.
Нулевая задержка:
console.log('script start')
setTimeout(() => {
console.log('timer 1 over')
}, 1000)
setTimeout(() => {
console.log('timer 2 over')
}, 0)
console.log('script end')
// script start
// script end
// timer 2 over
// timer 1 over
Здесь сначала будет напечатано "timer 2 over", а затем "timer 1 over" будет напечатано. Хотя таймер 1 обрабатывается потоком, запускаемым таймером, первым, обратный вызов таймера 2 будет добавлен в очередь сообщений первым.
Выше задержка таймера 2 равна 0 мс.Стандарт HTML5 предусматривает, что второй параметр setTimeout не должен быть меньше 4 (минимальное значение у разных браузеров будет разным), и задержка будет автоматически увеличиваться, поэтому «таймер 2 истек "по-прежнему будет в "конце сценария" позже.
Даже если задержка равна 0 мс, функция обратного вызова таймера 2 будет немедленно добавлена в очередь сообщений, и выполнение обратного вызова все равно должно ждать, пока стек выполнения не станет пустым (поток движка JS простаивает).
На самом деле, второй параметр setTimeout не представляет точное событие задержки выполнения обратного вызова, он может представлять только минимальное время задержки выполнения обратного вызова, потому что функция обратного вызова должна дождаться завершения задачи синхронизации в стеке выполнения. выполнение после входа в очередь сообщений, оно будет выполнено только тогда, когда стек пуст.
Макрозадачи и микрозадачи
Приведенного выше механизма достаточно в случае ES5, но у ES6 будут некоторые проблемы.
Обещания также используются для обработки асинхронности:
console.log('script start')
setTimeout(function() {
console.log('timer over')
}, 0)
Promise.resolve().then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('script end')
// script start
// script end
// promise1
// promise2
// timer over
WTF?? "обещание 1" "обещание 2" напечатано до "таймер закончился"?
Вот новая концепция:macrotask
(макрозадача) иmicrotask
(микрозадачи).
Все задачи разбиты наmacrotask
а такжеmicrotask
:
-
макрозадача: блок основного кода, setTimeout, setInterval и т. д. (Как видите, каждое событие в очереди событий — это макрозадача, которая теперь называется очередью макрозадач)
-
MicroTask: обещание, процесс. NextTick и т. Д.
Поток движка JS сначала выполняет основной блок кода.
Код, выполняемый каждый раз, когда стек выполнения является задачей макроса, включая очередь задач (очередь задач макроса), потому что после выполнения задачи макроса в стеке выполнения задача в очереди задач (очередь задач макроса) будет выбрана и добавлен в стек выполнения. Это также механизм цикла событий.
Когда Promise встречается при выполнении макрозадачи, микрозадача (обратный вызов в .then()) будет создана и добавлена в конец очереди микрозадач.
Микрозадача должна быть создана при выполнении задачи макроса, и перед запуском следующей задачи макроса браузер повторно отобразит страницу (task
>> 渲染
>> 下一个task
(взять одну из очереди задач)). При этом все микрозадачи в текущей очереди микрозадач будут выполняться после завершения выполнения предыдущей макрозадачи и до отрисовки страницы.
То есть после выполнения макрозадачи все микрозадачи, сгенерированные во время ее выполнения, будут выполнены (до рендеринга) перед повторным рендерингом и запуском следующей макрозадачи.
Это объясняет, что «обещание 1» и «обещание 2» были напечатаны до «таймера». "promise 1" "promise 2" добавляется в очередь микрозадач как микрозадача, а "timer over" добавляется в очередь макрозадач как макрозадача, они ожидают выполнения одновременно, но все микрозадачи в микрозадаче очередь Все задачи выполняются до запуска следующей задачи макроса.
В среде node приоритет process.nextTick выше, чем у Promise, то есть: после завершения макрозадачи сначала будет выполняться nextTickQueue в очереди микрозадач, а затем будет выполняться Promise в микрозадаче. быть казненным.
Механизм исполнения:
-
Выполнить задачу макроса (получить ее из очереди событий, если ее нет в стеке)
-
Если во время выполнения встречается микрозадача, добавьте ее в очередь задач микрозадачи.
-
После выполнения макрозадачи все микрозадачи в текущей очереди микрозадач выполняются немедленно (выполняются последовательно).
-
Выполняется текущая задача макроса, проверяется рендеринг, а затем поток графического интерфейса берет на себя рендеринг.
-
После рендеринга поток движка JS продолжается и запускает следующую задачу макроса (полученную из очереди задач макроса).
Суммировать
-
JavaScript — это однопоточный язык, поскольку изначально он был разработан для обработки взаимодействия браузера с веб-страницами. В браузере есть только один поток, отвечающий за интерпретацию и выполнение JavaScript (все говорят, что они однопоточные), то есть поток движка JS, но браузер также предоставляет другие потоки, такие как: поток триггера события, триггер таймера нить и т.д.
-
Асинхронный обычно относится к:
- сетевой запрос
- таймер
- Слушатель событий DOM
-
Механизм цикла событий:
- Поток движка JS будет поддерживать стек выполнения, а код синхронизации будет по очереди добавляться в стек выполнения, выполняться и извлекаться из стека.
- Когда поток движка JS встречает асинхронную функцию, он передает асинхронную функцию соответствующему Webapi и продолжает выполнять следующие задачи.
- Когда условия соблюдены, Webapi добавит соответствующий асинхронный обратный вызов в очередь сообщений и будет ждать выполнения.
- Когда стек выполнения пуст, поток движка JS извлечет функцию обратного вызова (если есть) в очереди сообщений и добавит ее в стек выполнения для выполнения.
- После завершения стек извлекается, стек выполнения снова пуст, и вышеперечисленные операции повторяются — это механизм цикла обработки событий.
-
над.
Ссылаться на: