написать впереди
js — это однопоточный язык программирования, а это означает, что когда js обрабатывает задачи, все задачи могут быть поставлены в очередь и выполняться только в одном потоке.Что, если определенная задача занимает много времени? Вы не можете дождаться его завершения, прежде чем выполнять следующий. Таким образом, внутри потока он делится на две очереди:
- Синхронизированная очередь задач
- Асинхронная очередь задач
Например: Например, если вы идете в банк по делам, вам нужно стоять в очереди с номером. Кассир банка обрабатывает бизнес один за другим.В это время кассир эквивалентен потоку js, очередь клиентов эквивалентна очереди задач синхронизации, а каждый человек эквивалентен одной задаче для кассира. Но в это время у вас вдруг зазвонил телефон, и вы полчаса шли отвечать на звонки. В это время кассир видит вашу ситуацию и сразу звонит следующему, а полученный вами номер недействителен, и вы можете стоять в очереди только с нулевым номером. На этом этапе вы направляетесь в очередь асинхронных задач. Когда люди перед вами закончат, кассир зовет вас заняться своими делами.В это время задачи в синхронной очереди выполнены, и основной поток будет обрабатывать задачи в асинхронной очереди.
Синхронные и асинхронные задачи
Упомянутая здесь асинхронная задача означает, что она содержит независимые задачи вне основного стека выполнения.宏任务和微任务
.
Давайте сначала рассмотрим простой пример, чтобы иметь простое представление о таком механизме выполнения:
console.log('start')
console.log('end')
Результат выполнения выше должен быть понятен всем.Сначала начало вывода, а затем конец вывода.Этот фрагмент кода попадет в очередь синхронизации и будет выполняться последовательно.
Итак, добавим кое-что:
console.log('start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
console.log('end')
В этом случае, когда стек вызова функции выполняется до setTimeout, setTimeout помещает функцию обратного вызова в асинхронную очередь в указанный момент времени, ждет выполнения задач в синхронной очереди и выполняет немедленно, поэтому результаты следующие: начало, конец, установка времени ожидания.
Но следует отметить, что принято считать, что познание исполнения таймингов setTimeout одностороннее, т.к. предполагается, что setTimeout выполняется через 2 секунды, но в очереди синхронизации есть функция, которая долго обрабатывается. выполнить, даже 1 секунду. Затем обратный вызов в setTimeout также будет ждать не менее 1 секунды после выполнения задач синхронизации, а затем выполняться. В это время время выполнения обратного вызова setTimeout превысит 2 секунды, то есть не менее 3 секунд.
Макрозадачи и микрозадачи
Макро-задачи и микро-задачи — это две другие очереди, которые независимы и находятся за пределами основного стека выполнения, и их можно концептуально разделить на асинхронные очереди задач. И эти очереди обрабатываются циклом событий js (EventLoop).
macro-task (макрозадача) и micro-task (микрозадача), в последнем стандарте они называются задачами и заданиями соответственно.
Поскольку я не заметил, когда писал статью, понятие макрозадач и микрозадач на самом деле неточное.Спасибо читателям в комментариях за указание, но поскольку в статье много интерпретаций макрозадач и микрозадач, это В настоящее время в статье по-прежнему используются макрозадачи, а задачи и микрозадачи относятся к задачам и заданиям соответственно. Но читатели должны понимать, что в спецификации нет понятия макрозадач, только задачи и задания.
К задачам макроса относятся:
- скрипт (общий код)
- setTimeout, setInterval, setImmediate,
- I/O
- Рендеринг пользовательского интерфейса Запросы Ajax не являются макрозадачами. Когда поток js встречает запрос Ajax, он передает запрос соответствующему потоку http для обработки. Как только запрос возвращает результат, он помещает соответствующий обратный вызов в очередь задач макроса и ждет чтобы запрос завершился.
К микрозадачам (работам) относятся:
- process.nextTick
- Promise
- Object.observe (устарело)
- MutationObserver (новая функция html5)
Их можно понимать как исполняемый код в контексте выполнения, который будет выполнен немедленно, но поместит соответствующие функции обратного вызова в соответствующую очередь задач (макроса-задачу и микро-задачу), что эквивалентно планировщику.
Разберемся с механизмом выполнения цикла событий: Цикл сначала начинается с задачи макроса, сталкивается со сценарием, генерирует контекст выполнения, начинает входить в стек выполнения, помещает исполняемый код в стек, последовательно выполняет код и завершает всплывающее окно стека. Когда в процессе выполнения встречается упомянутый выше планировщик, планировщик будет выполняться синхронно, и планировщик поместит ответственную за него задачу (функцию обратного вызова) в соответствующую очередь задач до тех пор, пока не будет опустошен основной стек выполнения, а затем начнет выполняться. выполнить микрозадачу очередь задач. После того, как микрозадача также будет очищена, снова начните с макрозадачи и все время циклируйте этот процесс.
Пример
Так много было сказано выше, давайте используем код, чтобы проверить, так ли это, начнем с более простого.
console.log('start')
setTimeout(function() {
console.log('timeout')
}, 0)
new Promise(function(resolve) {
console.log('promise')
resolve()
}).then(function() {
console.log('promise resolved')
})
console.log('end')
По вышеприведенным выводам проанализируем процесс исполнения:
- Установите контекст выполнения, войдите в стек выполнения, чтобы начать выполнение кода, и распечатайте
start
- Выполнить вниз, столкнуться с setTimeout, поместить функцию обратного вызова в очередь задач макроса и дождаться выполнения
- Продолжая движение вниз, появляется новый промис, функция обратного вызова которого не будет помещена в другие очереди задач, поэтому она будет выполняться синхронно, выводя
promise
, но после разрешения .then поместит свою внутреннюю функцию обратного вызова в очередь микрозадач - Выполняется до конца кода, распечатать
end
. В это время основной стек выполнения опустошается, и он начинает искать исполняемый код в очереди микрозадач. - Обнаружено, что в очередь микрозадач ранее был помещен код, а выполнение распечатывалось.
promise resolved
, первый цикл заканчивается - Запустите второй цикл, начиная с задачи макроса, проверьте, есть ли исполняемый код в очереди задач макроса, найдите его и распечатайте
timeout
Итак, порядок печати:start
-->promise
-->end
-->promise resolved
-->timeout
Выше приведен простой пример, который легче понять. Тогда давайте посмотрим на немного более сложный вариант (здесь напрямую используются китайские иероглифы, чтобы интуитивно указать время печати, чтобы не выглядеть трудоемким):
console.log('第一次循环主执行栈开始')
setTimeout(function() {
console.log('第二次循环开始,宏任务队列的第一个宏任务执行中')
new Promise(function(resolve) {
console.log('宏任务队列的第一个宏任务的微任务继续执行')
resolve()
}).then(function() {
console.log('第二次循环的微任务队列的微任务执行')
})
}, 0)
new Promise(function(resolve) {
console.log('第一次循环主执行栈进行中...')
resolve()
}).then(function() {
console.log('第一次循环微任务,第一次循环结束')
setTimeout(function() {
console.log('第二次循环的宏任务队列的第二个宏任务执行')
})
})
console.log('第一次循环主执行栈完成')
Также давайте проанализируем процесс выполнения:
-
первый цикл
- Войдите в стек выполнения, чтобы выполнить код, распечатайте
第一次循环主执行栈开始
- Когда встречается setTimeout, поместите обратный вызов в очередь задач макроса и дождитесь выполнения
- Процесс объявления промиса синхронный, печать
第一次循环主执行栈进行中...
, встретить .then после разрешения, поместить обратный вызов в очередь микрозадачи - Распечатать
第一次循环主执行栈完成
- Проверить, есть ли в очереди микрозадач исполняемый код, есть ли задача поставленная на третьем шаге, вывести
第一次循环微任务,第一次循环结束
, первый цикл завершается, и в то же время встречается setTimeout, и обратный вызов помещается в очередь задач макроса.
- Войдите в стек выполнения, чтобы выполнить код, распечатайте
-
второй цикл
- Начните с задачи макроса, проверьте очередь задач макроса и обнаружите, что есть две задачи макроса, которые представляют собой задачи, помещенные на второй шаг первого цикла и на пятый шаг первого цикла, сначала выполните первую задачу макроса, и распечатать
第二次循环开始,宏任务队列的第一个宏任务执行中
- Когда вы встретите обещание, напечатайте
宏任务队列的第一个宏任务继续执行
В это время он снова решается, и обратный вызов в. Затем будет введен в очередь Micro Task. Это первая задача в очереди макроса, которая еще не выполнена. - После того, как код синхронизации в первой задаче макроса будет выполнен, проверьте очередь микрозадач и обнаружите, что на втором шаге есть фрагмент кода, выполните печать
第二次循环的微任务队列的微任务执行
, выполняется первая задача макроса - Начать выполнение второй задачи макроса, распечатать
第二次循环的宏任务队列的第二个宏任务执行
, все очереди задач очищаются, и выполнение завершается
- Начните с задачи макроса, проверьте очередь задач макроса и обнаружите, что есть две задачи макроса, которые представляют собой задачи, помещенные на второй шаг первого цикла и на пятый шаг первого цикла, сначала выполните первую задачу макроса, и распечатать
Итак, порядок печати:
- Запускается первый цикл основного стека выполнения
- Выполняется первый цикл основного стека выполнения...
- Завершен первый цикл основного стека выполнения
- Первый цикл микрозадач, конец первого цикла
- Начинается второй цикл, выполняется первая задача макроса в очереди задач макроса.
- Микрозадача первой макрозадачи очереди макрозадач второго цикла продолжает выполняться
- MicroTask Выполнение второй петли MicroTask Queue
- Вторая макрозадача второго цикла
Взгляните на гифку, цикл событий виден невооруженным глазом (с крошечным временным промежутком между циклами)
Суммировать
Механизм выполнения js — обычное дело на собеседованиях, и он также очень сбивает с толку. Но я считаю, что если вы полностью понимаете механизм цикла событий и тщательно его анализируете, то не проблема столкнуться с таким вопросом на собеседовании. Когда я писал эту статью, я понял, что большая часть моего предыдущего понимания была ошибочной. Если вы считаете, что что-то не так, пожалуйста, помогите указать на это.
Добро пожаловать, чтобы обратить внимание на мою официальную учетную запись: один внешний интерфейс, один внешний интерфейс, я время от времени буду делиться знаниями о внешнем интерфейсе, которые я понимаю.