I. JavaScript - это отдельная нить
Почему? Во-первых, главной особенностью языка JavaScript является однопоточность. С точки зрения непрофессионала, одновременно можно делать только одну вещь. Тогда возникнут новые проблемы. Почему в JavaScript не может быть нескольких потоков?
Первоначально JavaScript был разработан для использования в браузерах, поэтому представьте, что JavaScript в браузерах является многопоточным.Например: предположим, что JavaScript имеет два потока одновременно, один поток добавляет содержимое в узел DOM, а другой поток удаляет этот узел. , за каким потоком должен следить браузер в это время?
Позже HTML5 предложил стандарт Web Worker, который позволяет JavaScript-скриптам создавать несколько потоков, но подпотоки полностью контролируются основным потоком и не должны работать с DOM, поэтому этот новый стандарт не меняет однопоточной природы. JavaScript
2. Почему JavaScript должен быть асинхронным?
Если в JavaScript нет асинхронности, то он может выполняться только сверху вниз, если предыдущая строка парсится долго, следующий код будет заблокирован, для пользователей блокировка означает "зависание", что приводит к Bad user Таким образом, в JavaScript есть асинхронное выполнение.3. Так как же добиться асинхронности?
Очередь задач: 1. Все задачи синхронизации выполняются в основном потоке, образуя стек выполнения (стек). 2. В дополнение к основному потоку также существует цикл событий очереди задач, а асинхронные задачи регистрируют функции в таблице событий, когда выполняются условия запуска (т. е. DOM, AJAX, setTimeout и setImmediate вернули результаты), они помещаются в очередь задач (Event Loop). 3. После того, как все синхронные задачи в стеке выполнения (стеке) будут выполнены, система прочитает очередь задач (цикл событий), чтобы увидеть, какие события в ней находятся.Эти соответствующие асинхронные задачи завершат состояние ожидания и перейдут к выполнению. стек.Начать выполнение. 4. Основной поток продолжает повторять третий шаг выше.
Пример 1:
console.log(1)
setTimeout(
function() {
console.log(2)
},
0)
console.log(3)
Результат бега: 1, 3, 2
Анализ кода:
1.console.log(1) — задача синхронизации, помещенная в основной поток
2.setTimeout — это асинхронная задача, которая помещается в таблицу событий и помещается в очередь задач (Event Loop) через 0 секунд.
3.console.log(3) — задача синхронизации, размещаемая в основном потоке.
Когда на консоли печатаются 1 и 3, основной поток переходит в цикл событий (очередь событий), чтобы увидеть, есть ли исполняемая функция, и выполняет функцию в setTimeout, которая является циклом событий.
4. Что такое цикл событий?
Основной поток считывает события из очереди задач (Event Loop), и этот процесс цикличен, поэтому весь механизм работы также называется Event Loop (цикл событий).
На приведенном выше рисунке при запуске основного потока генерируется куча и стек, а код в стеке вызывает различные внешние API, которые добавляют различные события (щелчок, загрузка) в «Цикл событий», выполнено). Пока код в стеке выполняется, основной поток будет читать «Очередь задач (цикл событий)» и по очереди выполнять функции обратного вызова, соответствующие этим событиям. Пример 2:
setTimeout(function() {
console.log('定时器开始啦')
});
new Promise(function(resolve) {
console.log('马上执行for循环啦');
for(var i =0; i <10000; i++) {
i ==99 &&resolve();
}
}).then(function() {
console.log('执行then函数啦')
});
console.log('代码执行结束');
Попробуйте проанализировать в соответствии с механизмом выполнения js, который мы только что изучили выше:
1.setTimeout является асинхронной задачей и помещается в таблицу событий
2. Новый Promise — это задача синхронизации, которая размещается в основном потоке и напрямую выводит console.log («выполнить цикл for сейчас»);
3. Функции в этом случае являются асинхронными задачами и помещаются в таблицу событий.
4.console.log('выполнение кода завершается'); это синхронный код, который размещается в основном потоке и выполняется напрямую
Итак, согласно результату анализа, цикл for выполняется немедленно --- выполнение кода завершается --- запускается таймер --- выполняется функция then
После запуска самого кода результат не такой, а:
Немедленно выполнить цикл for --- конец выполнения кода --- функция then выполняется --- запускается таймер
На самом деле деление по асинхронному и синхронному способу не является точным, а точный способ деления таков: макрозадача: скрипт (общий код), setTimeout, setInterval, setImmediate, ввод-вывод, отрисовка пользовательского интерфейса. микрозадача (микрозадача): process.nextTick, Promise, Object.observe (устарело), MutationObserver (новая функция html5)
Согласно этой классификации, механизм исполнения js:
1. Выполнить макрозадачу.Если в процессе вы встретите микрозадачу, поставьте ее в «очередь событий» микрозадачи
2. После завершения выполнения текущей макрозадачи будет проверена «очередь событий» микрозадачи, и все микрозадачи в ней будут выполняться последовательно.
3. Повторите два вышеуказанных шага в сочетании с рисунками 1 и 2, чтобы получить более точный механизм выполнения js.
Итак, для анализа примера 2:
1. Сначала выполнить макрозадачу под скриптом, при встрече с setTimeout поставить в "очередь" макрозадачи
2. При обнаружении нового промиса выполните его напрямую и напечатайте «немедленно выполнить цикл for».
3. Когда встречается метод then, это микрозадача, и он помещается в «очередь» микрозадачи.
4. При обнаружении console.log('окончание выполнения кода'); это синхронная задача, напрямую выводите "окончание выполнения кода"
5. После выполнения этого раунда макрозадач проверьте микрозадачи этого раунда и найдите функцию в методе then, и напечатайте «Execute then function».
6. На этом цикл событий этого раунда завершается.
7. В следующем раунде цикла сначала выполните задачу макроса, обнаружите, что в setTimeout есть функция в «очереди» задачи макроса, выполните и напечатайте «таймер запущен»
Таким образом, окончательная последовательность выполнения такова: немедленно выполнить цикл for --- выполнение кода заканчивается --- функция then выполняется --- запускается таймер
5. Таймер setTimeout() и setInterval()
Таймер указывает, как долго после выполнения определенного кода.Это называется функцией «таймер», то есть код, который выполняется регулярно.
Пример 3:
setTimeout(function(){
console.log('执行了')
},3000)
Обычно мы говорим: через 3 секунды функция из setTimeout будет выполнена, но это утверждение не является строгим.Точное объяснение таково: через 3 секунды функция из setTimeout будет помещена в очередь событий (Event Loop), и задачи в очереди событий (Event Loop) будут выполняться только тогда, когда основной поток простаивает, поэтому функция будет выполняться через 3 секунды только тогда, когда одновременно выполняются условия (ps: через 3 секунды и основной поток праздный).
Если основной поток выполняет много контента и время выполнения превышает 3 секунды, например, стек выполнения в основном потоке выполняется 10 секунд, то эта функция может быть выполнена только через 10 секунд.
6. Цикл событий Node.js
Node.js также является однопоточным циклом обработки событий, но его механизм работы отличается от среды браузера.
Как показано на рисунке, механизм работы Node.js выглядит следующим образом:
1. Движок V8 анализирует сценарии JavaScript.
2. Разобранный код вызывает Node API.
3. Библиотека libuv отвечает за выполнение Node API. Он назначает разные задачи разным потокам для формирования события.
Цикл (цикл событий), который асинхронно возвращает результат выполнения задачи в движок V8.
4. Движок V8 возвращает результат пользователю.
В дополнение к двум методам setTimeout и setInterval Node.js также предоставляет два других метода, связанных с «очередью задач»: process.nextTick и setImmediate. Они могут помочь нам углубить наше понимание «очередей задач». nextTick setНемедленная разница и контакт nextTick : поместите функцию обратного вызова в нижнюю часть текущего стека выполнения, и несколько операторов process.nextTick всегда выполняются один раз в текущем «стеке выполнения». setImmediate : поместите функцию обратного вызова в конец очереди событий (цикл событий), а несколько setImmediate могут потребовать несколько циклов для завершения выполнения.
Пример 4:
process.nextTick(function A() {
console.log(1);
process.nextTick(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
В приведенном выше коде, поскольку функция обратного вызова, указанная методом process.nextTick, всегда запускается в конце текущего «стека выполнения», не только функция обратного вызова A выполняется до истечения времени ожидания функции обратного вызова, заданного setTimeout, но также и функция обратного вызова A. функция B выполняется до истечения времени ожидания. Это означает, что если имеется несколько операторов process.nextTick (независимо от того, вложены они или нет), все они будут выполняться в текущем «стеке выполнения». Таким образом, результат: 1, 2, «ТАЙМ-АУТ ВЫПОЛНЕН».
Теперь давайте снова посмотрим на setImmediate Пример 5:
setImmediate(function A() {
console.log(1);
setImmediate(function B(){
console.log(2);
});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
В приведенном выше коде setImmediate и setTimeout(fn,0) добавляют функцию обратного вызова A и время ожидания, которые запускаются в следующей очереди событий (цикл событий). Итак, какая функция обратного вызова выполняется первой? Ответ не уверен. Текущим результатом может быть 1, TIMEOUT FIRED, 2 или TIMEOUT FIRED, 1, 2.
Как ни странно, в документации Node.js говорится, что функция обратного вызова, указанная setImmediate, всегда предшествует setTimeout. На самом деле это происходит только при рекурсивных вызовах.
Пример 6:
setImmediate(function (){
setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
});
В приведенном выше коде setImmediate и setTimeout инкапсулированы в setImmediate, и его текущий результат всегда равен 1, TIMEOUT FIRED, 2. В это время функция A должна быть запущена до истечения времени ожидания. Что касается второй строки после TIMEOUT FIRED (то есть функция B срабатывает после тайм-аута), это связано с тем, что setImmediate всегда регистрирует событие в следующем раунде очереди событий (Event Loop), поэтому функция A и тайм-аут выполняются в одном и том же раунд цикла, в то время как функция B выполняется в следующем раунде цикла
Кроме того, поскольку функция обратного вызова, указанная в process.nextTick, запускается в этом «цикле событий», а указанная setImmediate запускается в следующем «цикле событий», очевидно, что первая всегда происходит раньше, чем вторая, а Эффективность выполнения также высока (поскольку нет необходимости проверять «очередь задач»).