Сначала посмотрите на код:
console.log(1);
setTimeout(function () {
console.log(2);
new Promise(function (resolve, reject) {
console.log(3);
resolve();
console.log(4);
}).then(function () {
console.log(5);
});
});
function fn() {
console.log(6);
setTimeout(function () {
console.log(7);
}, 50);
}
new Promise(function (resolve, reject) {
console.log(8);
resolve();
console.log(9);
}).then(function () {
console.log(10);
});
fn();
console.log(11);
// 以下代码需要在 node 环境中执行
process.nextTick(function () {
console.log(12);
});
setImmediate(function () {
console.log(13);
});
Подумайте, может дать точный порядок вывода?
Давайте узнаем о моментах знаний, связанных с циклом событий один за другим, и, наконец, анализируют окончательную выходную последовательность этого кода на шаг за шагом.
JavaScript является однопоточным
Прежде всего, давайте разберемся с концепциями и отношениями процессов и потоков:
- обработать:Работающая программа — это процесс, такой как браузер, который вы запускаете, у него будет процесс.
- Нить:Кусок кода в программе, которая работает независимо.процесспо одному или множествунитьКомпозиция, поток отвечает за выполнение кода.
Мы все знаем, что JavaScript является однопоточным, поэтому, поскольку есть один поток, есть несколько потоков.Давайте сначала посмотрим на разницу между одним потоком и многопоточным:
- Один поток:Выполнять от начала до конца, строка за строкой, если одна строка кода сообщает об ошибке, остальная часть кода выполняться не будет. В то же время код легко заблокировать.
- Многопоточность:Код выполняется в разных средах, и каждый поток независим и не влияет друг на друга, чтобы избежать блокировки.
Так почему же JavaScript однопоточный?
Единый поток JavaScript в соответствии с его назначением. В качестве языка сценариев браузера JavaScript в основном используется для взаимодействия с пользователем и управления DOM. Это определяет, что это может быть только один поток, иначе возникнут сложные проблемы с синхронизацией. Например, предположим, что в JavaScript есть два потока одновременно, один поток добавляет содержимое в узел DOM, а другой поток удаляет этот узел, какой поток должен выбрать браузер?
Таким образом, чтобы избежать сложности, JavaScript с самого начала был однопоточным, что было основной особенностью языка и не изменится в будущем.
Чтобы воспользоваться вычислительной мощностью многоядерных процессоров, HTML5 предлагает стандарт Web Worker, который позволяет сценариям JavaScript создавать несколько потоков, но подпотоки полностью контролируются основным потоком и не должны манипулировать DOM. Таким образом, этот новый стандарт не меняет однопоточной природы JavaScript.
Стек выполнения, очередь задач
На приведенном выше рисунке при работе основного потока генерируется куча и стек, код в стеке вызывает различные внешние API, и они добавляют различные события (DOM Event, ajax, setTimeout.com) в «очередь задач». "...). Пока код в стеке выполняется, основной поток будет читать очередь задач и по очереди выполнять функции обратного вызова, соответствующие этим событиям.
куча:
Объекты размещаются в куче, которая используется для представления в значительной степени неструктурированной области памяти.
Стек выполнения (стек):
Запустите синхронный код. Код в стеке выполнения (синхронные задачи) всегда выполняется до чтения «очереди задач» (асинхронные задачи).
Очередь задач (очередь обратного вызова):
«Очередь задач» — это очередь событий (которая также может пониматься как очередь сообщений).Когда устройство ввода-вывода завершает задачу, в «очередь задач» добавляется событие, указывающее, что связанная асинхронная задача может войти в очередь задач. «стек выполнения». Основной поток читает «очередь задач», то есть читает, какие события в ней находятся.
События в «очереди задач», помимо событий IO-устройства, также включают в себя некоторые генерируемые пользователем события (такие как щелчки мышью, прокрутка страницы и т. д.). Пока указана функция обратного вызова, эти события будут поступать в «очередь задач», когда они происходят, ожидая чтения основным потоком.
Так называемая «функция обратного вызова» (callback) — это код, выполнение которого будет приостановлено основным потоком. В асинхронной задаче должна быть указана функция обратного вызова.Когда основной поток начинает выполнять асинхронную задачу, выполняется соответствующая функция обратного вызова.
«Очередь задач» — это структура данных «первым поступил — первым обслужен», и события в начале считываются главным потоком в первую очередь. Процесс чтения основного потока в основном автоматический.Как только стек выполнения опустеет, первое событие в «очереди задач» автоматически войдет в основной поток. Однако из-за упомянутой ниже функции «таймер» основной поток должен сначала проверить время выполнения, а некоторые события могут вернуться в основной поток только по истечении указанного времени.
Синхронные задачи, асинхронные задачи, макрозадачи, микрозадачи
Однопоточный означает, что все задачи должны быть поставлены в очередь, а следующая задача будет выполняться после завершения предыдущей задачи. Если первая задача занимает много времени, вторая задача должна ждать вечно.
Если очередь возникает из-за большого объема вычислений, а ЦП слишком занят, это нормально, но часто ЦП простаивает, потому что устройства ввода-вывода (устройства ввода и вывода) очень медленные (например, операции Ajax для чтения данных). из сети), нужно дождаться выхода результата, а затем перейти к следующему выполнению.
Разработчики языка JavaScript поняли, что в это время основной поток может полностью игнорировать устройство ввода-вывода, приостанавливать ожидающие задачи и сначала запускать задачи в конце. Дождитесь, пока устройство ввода-вывода вернет результат, затем вернитесь и продолжите выполнение приостановленной задачи.
Поэтому в широком смысле все задачи в JavaScript можно разделить на два типа, одна — синхронная задача (synchronous), а другая — асинхронная задача (asynchronous). Синхронные задачи относятся к задачам, поставленным в очередь для выполнения в основном потоке, и следующая задача может быть выполнена только после выполнения предыдущей задачи; асинхронные задачи относятся к задачам, которые не входят в основной поток, а входят в «очередь задач» ( очередь задач) Задача, только "очередь задач" уведомляет основной поток о том, что асинхронная задача может быть выполнена, задача войдет в основной поток для выполнения.
В частности, рабочий механизм асинхронного выполнения выглядит следующим образом (то же самое верно и для синхронного выполнения, поскольку его можно рассматривать как асинхронное выполнение без асинхронных задач):
(1)所有同步任务都在主线程上执行,形成一个"执行栈"(execution context stack);
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件;
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会取出"任务队列"中事件所对应的回调函数进入"执行栈",开始执行;
(4)主线程不断重复上面的第三步。
В дополнение к широкому определению мы можем определить задачи более точно и разделить их на макрозадачи и микрозадачи:
- Макро-задача:Включая общий скрипт кода, операции setTimeout, setInterval, ajax, dom.
- Микрозадачи: Promise
В частности, механизм выполнения макрозадач и микрозадач следующий:
(1)首先,将"执行栈"最开始的所有同步代码(宏任务)执行完成;
(2)检查是否有微任务,如有则执行所有的微任务;
(3)取出"任务队列"中事件所对应的回调函数(宏任务)进入"执行栈"并执行完成;
(4)再检查是否有微任务,如有则执行所有的微任务;
(5)主线程不断重复上面的(3)(4)步。
Для двух вышеуказанных операционных механизмов основной поток считывает события из «очереди задач».Этот процесс является циклическим, поэтому весь операционный механизм также называетсяЦикл событий.
setTimeout(), setInterval()
Две функции, setTimeout() и setInterval(), имеют точно такой же внутренний механизм работы, разница в том, что код, заданный первой, выполняется один раз, а второй выполняется повторно.
Задачи, создаваемые setTimeout() и setInterval(),асинхронная задача, также принадлежитЗадача макроса.
setTimeout() принимает два параметра: первый — это функция обратного вызова, а второй — количество миллисекунд для задержки выполнения. setInterval() принимает два параметра: первый — это функция обратного вызова, а второй — количество миллисекунд для повторения.
Если второй параметр установлен в 0 или не установлен, это означаетВместо немедленного выполнения задача указывается для выполнения в самое раннее время простоя, доступное для основного потока, то есть как можно раньше. Он добавляет событие в конец «очереди задач», поэтому оно не будет выполнено до тех пор, пока не будут обработаны как задача синхронизации, так и существующие события «очереди задач».
Следовательно, время, заданное вторым параметром функций setTimeout() и setInterval(), не является абсолютным. Его необходимо определять в соответствии с окончательным временем выполнения текущего кода. Короче говоря, если текущее время выполнения кода (например, выполнение 200 мс) Превышает отложенное выполнение (setTimeout(fn, 100)) или время повторного выполнения (setInterval(fn, 100)), затем setTimeout(fn, 100) и setTimeout(fn, 0) Нет никакой разницы, setInterval(fn , 100) и setInterval(fn, 0) ничем не отличаются.
Promise
Промис относительно особенный, функция обратного вызова, переданная в new Promise(), будетвыполнить немедленно, но этоthen()метод находится вПосле стека выполнения перед очередью задачисполнено, оно принадлежитмикрозадачи.
process.nextTick
process.nextTick — это метод, связанный с «очередью задач», предоставляемой Node.js. Создаваемые им задачи помещаются вхвост стека выполнения, не принадлежитзадача макросаа такжемикрозадачи, значит его задачаВсегда происходит перед всеми асинхронными задачами.
setImmediate
setImmediate — это еще один метод, связанный с «очередью задач», предоставляемой Node.js.Сгенерированные им задачи добавляются в конец «очереди задач».setTimeout(fn, 0)Очень похоже, но приоритет setTimeout имеет приоритет над setImmediate.
Иногда порядок выполнения setTimeout будет перед setImmediate, а иногда будет позади setImmediate, это не ошибка node.js, это потому, что хотя второй параметр setTimeout установлен в 0 или не установлен, но в исходном коде setTimeout будет указано определенное количество миллисекунд (узел — 1 мс, браузер — 4 мс), и поскольку на текущее время выполнения кода влияет среда выполнения, время выполнения колеблется, если текущий выполняемый код меньше указанного значения, setTimeout Прежде чем наступит время отложить выполнение, сначала будет выполнен setImmediate.Если текущий выполняемый код превышает указанное значение, setTimeout будет выполнен перед setImmediate.
приоритет
Благодаря приведенному выше введению мы можем получить приоритет выполнения кода:
Синхронный код (макрозадача) > process.nextTick > Promise (микрозадача) > setTimeout (fn), setInterval (fn) (макрозадача) > setImmediate (макрозадача) > setTimeout (fn, время), setInterval (fn, время) , где время>0
Разбор кода
Возвращаясь к коду, приведенному в начале, разберем его пошагово:
Запустите «Стек выполнения» в коде:
console.log(1);
// setTimeout(function () { // 作为宏任务,暂不执行,放到任务队列中(1)
// console.log(2);
//
// new Promise(function (resolve, reject) {
// console.log(3);
// resolve();
// console.log(4);
// }).then(function () {
// console.log(5);
// });
// });
function fn() {
console.log(6);
//setTimeout(function () { // 作为宏任务,暂不执行,放到任务队列中(3)
// console.log(7);
//}, 50);
}
new Promise(function (resolve, reject) {
console.log(8);
resolve();
console.log(9);
})
// .then(function () { // 作为微任务,暂不执行
// console.log(10);
// });
fn();
console.log(11);
process.nextTick(function () {
console.log(12);
});
// setImmediate(function () { // 作为宏任务,暂不执行,放到任务队列中(2)
// console.log(13);
// });
Результат:1 8 9 6 11 12
Запустите микрозадачу:
new Promise(function (resolve, reject) {
// console.log(8); // 已执行
// resolve(); // 已执行
// console.log(9); // 已执行
})
.then(function () {
console.log(10);
});
На данный момент вывод:10
Прочитайте функцию обратного вызова «очереди задач» в «стек выполнения»:
setTimeout(function () {
console.log(2);
new Promise(function (resolve, reject) {
console.log(3);
resolve();
console.log(4);
})
//.then(function () { // 作为微任务,暂不执行
// console.log(5);
//});
});
На данный момент вывод:2 3 4
Запустите микрозадачи:
setTimeout(function () {
// console.log(2); // 已执行
new Promise(function (resolve, reject) {
// console.log(3); // 已执行
// resolve(); // 已执行
// console.log(4); // 已执行
})
.then(function () {
console.log(5);
});
});
На данный момент вывод:5
Затем прочитайте функцию обратного вызова «очереди задач» в «стек выполнения».:
setImmediate(function () {
console.log(13);
});
На данный момент вывод:13
Запустите микрозадачу:
без
Затем прочитайте функцию обратного вызова «очереди задач» в «стек выполнения»:
// function fn() { // 已执行
// console.log(6); // 已执行
setTimeout(function () {
console.log(7);
}, 50);
// }
На данный момент вывод:7
Запустите микрозадачу:
без
Таким образом, окончательный порядок вывода:1 8 9 6 11 12 10 2 3 4 5 13 7
использованная литература
- Подробное объяснение цикла событий в javascript
- Подробное объяснение механизма работы JavaScript: снова поговорим о цикле событий
- Модель параллелизма и цикл событий
- На этот раз досконально изучите механизм выполнения JavaScript.
- Цикл событий исследования узла (2) — разница между setTimeout/setImmediate/process.nextTick
Пожалуйста, указывайте источник при перепечатке, спасибо!