参考1:https://www.cnblogs.com/lucy-xyy/p/11652286.html
参考2:https://www.cnblogs.com/yugege/p/9598265.html
事件循环机制
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事,但是一个任务耗时太长,那么后面的任务就需要等待,为了解决这种情况,将任务分为了同步任务和异步任务,而异步任务又可以分为微任务和宏任务。
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 Event Queue (任务队列)。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
所以异步操作都是放到事件循环队列里面,等待主执行栈来执行的,也并没有专门的异步执行线程。
宏任务和微任务
task分为两大类, 分别是 Macro Task (宏任务)和 Micro Task(微任务), 并且每个宏任务结束后, 都要清空所有的微任务。
macro task宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
micro task微任务主要包含:Promise函数的 .then 语句、 .catch 语句、 .finally 语句,async函数await表达式后面的语句、MutaionObserver、process.nextTick(Node.js 环境)
补充:在 node 环境下,process.nextTick 的优先级高于 Promise函数中的then语句
从图中可以看到,宏任务执行顺序优先于微任务,所以JS执行顺序为:script>清空微任务>宏任务>清空微任务>宏任务>清空微任务……直至清空任务队列
举个栗子
async function async1() {
console.log(2); // 顺序4.同步代码最先执行,输出2
await fn(); // 顺序5:await会阻塞await后面代码的执行(将await后面的代码,也就是这里的console.log(7)变成微任务,放进任务队列等待执行),但不阻塞await表达式,同时await会跳出async函数
console.log(7); // 顺序10:await执行完毕,执行await后的微任务,输出7,然后执行后续的同步代码及await时生成的微任务
}
function fn() {
// 顺序6:执行fn,输出3,await跳出async函数让出线程,执行async后面的代码
console.log(3);
}
console.log(1); // 顺序1.由上而下,同步代码最先执行,输出1
setTimeout(function () { // 顺序2.setTimeout为宏任务,进入任务队列进行等待执行
console.log(10); // 顺序13.无同步代码、无微任务,开始执行下一个宏任务,输出10
}, 0)
async1(); // 顺序3.同步代码,执行方法async1
new Promise(function (resolve) {
console.log(4); // 顺序7:执行Promise同步代码,输出4,记录.then为微任务1,进入队列等待执行
resolve();
}).then(function () {
console.log(8); // 顺序11.无同步代码,依次执行剩余微任务,输出8
});
new Promise(function (resolve) {
console.log(5); // 顺序8:执行Promise同步代码,输出5,记录.then为微任务2,进入队列等待执行
resolve();
}).then(function () {
console.log(9); // 顺序12.无同步代码,执行剩余微任务2,输出9
});
console.log(6); // 顺序9:执行同步代码,输出6