Теги: узел механизма событий цикла событий
Оригинальный адрес:Tasks, microtasks, queues and schedules
Поскольку в предыдущей статье упоминались микрозадачи и макрозадачи, вот подробное дополнение: Без лишних слов, перейдем непосредственно к тексту:
[1] Эта статья в основном основана на сводке интернет-ресурсов, если что-то не так, поправьте. [2] Профессиональные термины, которые необходимо знать: синхронный: синхронная задача, асинхронный: асинхронная задача, очередь задач/очередь обратного вызова: очередь задач, стек контекста выполнения: стек выполнения, куча: куча, стек: стек, макрозадача: макрозадача , микрозадача: микрозадача
Для начала нам нужно знать две вещи:
- JavaScript — это однопоточный язык
- Цикл событий — это механизм выполнения javascript.
цикл событий javascript
js — это один поток, точно так же, как студенты стоят в очереди, чтобы сходить в туалет, студенты должны стоять в очереди, чтобы сходить в туалет один за другим, и аналогичным образом задачи js должны выполняться одна за другой. Если одна задача выполняется слишком долго, вторая задача также должна ждать. Итак, вопрос в том, если мы хотим просматривать новости, но сверхчеткие изображения, содержащиеся в новостях, загружаются очень медленно, должна ли наша веб-страница зависать до полного отображения изображений? Итак, умные программисты делят задачи на две категории:
- Синхронизировать задачу
- асинхронная задача
Как видно из картинки выполнение метода добавит среду выполнения метода в стек выполнения.В этой среде выполнения могут быть вызваны и другие методы, или даже он сам.Результат просто добавить еще одно выполнение среды в стек выполнения. Этот процесс может продолжаться бесконечно, если только не произойдет переполнения стека, то есть превышен максимальный объем памяти, который можно использовать.
Когда мы открываем веб-сайт, процесс рендеринга веб-страницы представляет собой множество синхронных задач, таких как рендеринг скелета страницы и элементов страницы. Задачи, которые занимают много ресурсов и занимают много времени, например загрузка изображений и музыки, являются асинхронными задачами. Существует строгое буквальное определение этой части, но цель этой статьи — полностью понять механизм выполнения с минимальными затратами на обучение.
Сначала посмотрите на кусок кода:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
Какой порядок печати? Правильный ответ: начало сценария, конец сценария, обещание1, обещание2, setTimeout Обведено. . .
Почему возникает этот порядок печати?
- Следующая карта (данная карта загружена с веб-сайта)
Интерпретация:
- Синхронные и асинхронные задачи входят в разные «места» выполнения, синхронно входят в основной поток, асинхронно входят в таблицу событий и регистрируют функции.
- Когда указанное действие будет выполнено, таблица событий переместит эту функцию в очередь событий.
- После того, как задача в основном потоке опустеет, она отправится в Очередь событий, чтобы прочитать соответствующую функцию и войти в основной поток для выполнения.
- Вышеупомянутый процесс будет продолжать повторяться, что часто называют циклом событий.
Мы не можем не спросить, как мы узнаем, что стек выполнения основного потока пуст? В движке js есть процесс мониторинга, и он будет постоянно проверять, пуст ли стек выполнения основного потока.Как только он будет пуст, он перейдет в очередь событий, чтобы проверить, есть ли функции, ожидающие вызова.
Посмотрите на код:
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
Выше находится простой код запроса AJAX:
- ajax входит в таблицу событий и регистрирует успех функции обратного вызова.
- Выполните console.log('конец выполнения кода').
- Событие ajax завершено, и успешное выполнение функции обратного вызова попадает в очередь событий.
- Основной поток считывает успешную функцию обратного вызова из очереди событий и выполняет ее.
Я полагаю, что благодаря приведенному выше тексту и коду у вас есть предварительное представление о порядке выполнения js.
Микрозазорки, макро задачи?
Микро-пробация и макросы - это все асинхронные задачи, все они принадлежат к очереди, главное отличие между порядком их выполнения, направлением и значением цикла событий. Так вчем разница между ними?
Краткое содержание статьи брата Наггетс (ssssyoki): Так что, кажется, мой ответ правильный. Но у асинхронного js есть механизм, то есть при встрече с макрозадачей сначала выполнить макрозадачу, поместить макрозадачу в очередь событий, а затем выполнить микрозадачу и поставить микрозадачу в очередь событий. что эти две очереди не являются очередью. Когда будете его вынимать, сначала возьмите функцию обратного вызова из микрозадачи, а затем возьмите функцию обратного вызова задачи макроса из очереди задачи макроса. Когда я это увидел, меня зацепило, и там была такая неряшливая операция.
- Макрозадачи, как правило, включают в себя общий сценарий кода, setTimeout, setInterval, setImmediate.
- Микрозадачи: родной промис (некоторые реализованные промисы помещают метод then в задачу макроса), process.nextTick, Object.observe (устарело), MutationObserver Просто помни.
- что такое процесс?
Никакой ерунды, посмотрите на следующий пример:
setTimeout
Знаменитый setTimeout не нуждается в дополнительных словах.Первое впечатление о нем у всех такое, что асинхронное выполнение может быть отложено.Мы часто реализуем отложенное выполнение на 3 секунды так:
setTimeout(() => {
console.log('延时3秒');
},3000)
Постепенно setTimeout стали использовать все чаще, и тоже появились проблемы.Иногда четко написано, что задержка 3 секунды, а реальная функция выполняется за 5 или 6 секунд.Что происходит?
setTimeout(() => {
task();
},3000)
console.log('执行console');
Согласно нашему выводу выше, setTimeout является асинхронным, и синхронная задача console.log должна выполняться первой, поэтому наш вывод таков:
// 执行console
// task()
Проверьте, результат правильный! Затем мы модифицируем предыдущий код:
setTimeout(() => {
task()
},3000)
sleep(10000000)
На первый взгляд, это почти то же самое, но когда мы выполняем этот код в хроме, мы обнаруживаем, что время, необходимое консоли для выполнения задачи(), намного больше, чем 3 секунды.Задержка составляет три секунды.Почему это теперь так долго?
В это время нам нужно переосмыслитьsetTimeout
Определение. Давайте сначала поговорим о том, как выполняется приведенный выше код:
- task() входит в таблицу событий и регистрируется, и начинается отсчет времени.
- Выполните функцию сна, очень медленно, очень медленно, и время продолжается.
- 3 секунды истекли, тайм-аут события синхронизации завершен, задача () входит в очередь событий, но спящий режим слишком медленный, он еще не выполнен, поэтому мне нужно подождать.
- Sleep, наконец, выполняется, и задача () наконец входит в основной поток из очереди событий для выполнения.
После завершения вышеуказанного процесса мы знаем, что функция setTimeout добавляет задачу для выполнения (task() в этом примере) в очередь событий через указанное время, и поскольку это однопоточная задача, ее необходимо выполняется одна за другой. Предыдущая задача занимает слишком много времени, поэтому вам остается только ждать, в результате чего реальное время задержки намного превышает 3 секунды.
Мы также часто сталкиваемсяsetTimeout(fn,0)
Что значит выполнить такой код через 0 секунд? Можно ли его выполнить немедленно?
Ответ - нет,setTimeout(fn,0)
Смысл в том, чтобы указать, что задача выполняется в самое раннее доступное время простоя основного потока, а это означает, что нет необходимости ждать, сколько секунд, пока задачи синхронизации в стеке выполнения основного потока находятся все выполнено, стек будет выполнен немедленно. Например:
//代码1
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},0);
//代码2
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},3000);
Код 1 выводится:
先执行这里
执行啦
Вывод кода 2:
//先执行这里
// ... 3s later
// 执行啦
Одна вещь, которую следует добавить о setTimeout, заключается в том, что даже если основной поток пуст, 0 миллисекунд на самом деле недостижим. Согласно стандартам HTML, минимум составляет 4 миллисекунды. Заинтересованные студенты могут узнать об этом самостоятельно.
setInterval
вышеизложенное закончилосьsetTimeout
, конечно, чтобы не упустить своего брата-близнецаsetInterval
. Они похожи, за исключением того, что последний является выполнением цикла. Для исполнительного листа,setInterval
Зарегистрированная функция будет помещаться в Очередь событий каждое указанное время.Если предыдущая задача занимает слишком много времени, ее также нужно подождать.
Единственное, что следует отметить, это то,对于setInterval(fn,ms)
Например, мы уже знаем, что fn не выполняется каждые мс секунд, но каждые мс секунд fn будет попадать в очередь событий. Как только время выполнения функции обратного вызова fn из setInterval превышает время задержки ms, временной интервал вообще отсутствует. Читателей просят внимательно смаковать это предложение.
Обещание и процесс.nextTick(обратный вызов)
- Определение и функции Promise в этой статье повторяться не будут, вы можете их изучитьОбещание учителя Жуань Ифэн
- А process.nextTick(callback) похож на версию setTimeout для node.js, которая вызывает функцию обратного вызова обратного вызова в следующем цикле цикла событий.
Различные типы задач будут поступать в соответствующую очередь событий, напримерsetTimeout
а такжеsetInterval
попадет в ту же очередь событий.
См. пример:
setTimeout(()=>{
console.log('setTimeout1')
},0)
let p = new Promise((resolve,reject)=>{
console.log('Promise1')
resolve()
})
p.then(()=>{
console.log('Promise2')
})
Окончательный вывод: Promise1, Promise2, setTimeout1.
Promise1 в параметре Promise выполняется синхронно Во-вторых, поскольку Promise — это микрозадачи, он очистит очереди микрозадач после выполнения задачи синхронизации. Наконец, после очистки микрозадачи перейдите в очередь макрозадач, чтобы получить значение.
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0)
На этот раз он вложен, вы можете видеть, что окончательный вывод Promise1, setTimeout1, Promise2, setTimeout2
- После того, как задача синхронизации исходного стека выполнения будет выполнена, она перейдет к очередям микрозадач, чтобы найти
Пустые очереди микрозадач, вывод
Promise1
, и одновременно будет сгенерирована асинхронная задача setTimeout1 - Перейдите в очередь задач макроса, чтобы увидеть, что очередь установлена перед setTimeout1 перед setTimeout2, потому что setTimeout1 запускает асинхронное выполнение в начале стека выполнения, поэтому выходные данные
setTimeout1
- Когда setTimeout1 выполняется, микрозадача Promise2 будет сгенерирована, помещена в очереди микрозадач, а затем еще один цикл для очистки очередей микрозадач, вывод
Promise2
- После очистки очередей микрозадач она перейдет в очередь задач макросов, чтобы получить еще одну.
setTimeout2
Как показано ниже:
Наконец, давайте проанализируем более сложный код, чтобы увидеть, действительно ли вы владеете механизмом выполнения js:
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
Первый раунд анализа процесса цикла событий выглядит следующим образом:
- Общий сценарий входит в основной поток в качестве первой задачи макроса, встречает console.log и выводит
1
. - При обнаружении setTimeout его функция обратного вызова отправляется в очередь задач макроса Event Queue. Мы временно записываем как
setTimeout1
. - Когда встречается process.nextTick(), его функция обратного вызова отправляется в очередь событий микрозадачи. мы записываем как
process1
. - Когда встречается промис, новый промис выполняется напрямую и выводится 7. затем отправляется в очередь событий микрозадачи. мы записываем как
then1
. - Я снова столкнулся с setTimeout, и его функция обратного вызова была распределена на макрозадачу Event Queue, которую мы записали как
setTimeout2
.
Задача макроса Очередь событий | Очередь событий микрозадач |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
- В приведенной выше таблице показано состояние каждой очереди событий в конце первого раунда макрозадач цикла событий, которые были выведены в это время.
1
а также7
.
Мы нашли две микрозадачи, process1 и then1.
- Выполнить процесс1, вывод
6
. - Выполнить then1, вывод
8
.
Что ж, первый раунд цикла событий официально завершен, и результатом этого раунда является вывод1,7,6,8
. Затем начинается второй раунд временного цикла с макрозадачи setTimeout1:
- выводить первым
2
. Затем был обнаружен process.nextTick(), который также был передан в очередь событий микрозадачи, которая была записана как process2. - новое обещание немедленно выполняет вывод
4
, then также распространяется на очередь событий микрозадачи, обозначаемую как then2
Задача макроса Очередь событий | Очередь событий микрозадач |
---|---|
setTimeout2 | process3 |
then3 |
-
Третий раунд выполнения макрозадач цикла событий заканчивается, и выполняются две микрозадачи process3 и then3.
-
выход
10
. -
выход
12
. -
Третий раунд цикла событий заканчивается, и третий раунд вывода
9,11,10,12
. -
Весь код имеет в общей сложности три цикла событий, и полный вывод
1,7,6,8,2,4,3,5,9,11,10,12
. (Обратите внимание, что мониторинг событий в среде узла зависит от libuv, а среда внешнего интерфейса не совсем такая же, и в порядке вывода могут быть ошибки)
конец
Я надеюсь, что вы прочитали эту статью иметь урожай ...