事件循环 Event Loop
概念: 因为JavaScript是单线程,也就是同一时间只能执行一个任务,所以所有任务都要排队执行,如果前一个任务执行时间过长,后面的任务就必须等待它执行完毕。
任务分类: 同步任务(synchronous)和异步任务(asynchronous)
同步任务(synchronous)
概念: 主线程上排队执行的任务,只有前一个任务执行完毕,才可以执行后一个任务
异步任务(asynchronous)
概念: 不进入主线程,而进入任务队列的任务,只有当任务队列通知主线程某个异步任务可以执行了,该任务才可以进入主线程执行。
主线程运行流程
- 所有同步任务都在主线程上执行,形成一个执行上下文栈(Execution Context Stack)。
- 当主线程上的所有任务都执行完毕后,才会从任务队列中获取任务到主线程上来执行。
- 不断重复执行上一步。
任务队列
分类: 任务队列中的任务分为宏任务(Macrotask)和微任务(Microtask)。
任务队列运行流程
流程: 一次事件循环只执行宏任务队列队首的任务,这个宏任务执行完毕后再执行新微任务队列中的所有任务。
宏任务(Macrotask)
常见的宏任务:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel
微任务(Microtask)
常见的微任务:Promise.then, Promise.catch,async函数中第一个await之后的所有代码(不包括第一个)
总结所有流程
- 先按代码正常顺序从上往下执行主线程
-
遇到
async
函数,如果只是声明而不是用()
调用,就不执行,如果是调用就先执行async
函数,如果里面有await
函数,就直接执行到第一个await
函数结束,执行完后这个函数,把后面的所有包括其他的await
函数放到微任务队列中。 -
遇到
Promise
函数,不论是直接声明还是赋值给一个新变量都直接执行里面的代码,停在执行到resolve()
或是reject()
之前,也就是then
或catch
之前,把后面的所有代码放到微任务队列中。 -
遇到
setTimeout()
不执行直接放到任务队列的宏任务中。
-
主线程执行完后,先到任务队列的微任务中查找是否有任务,如果有就执行里面全部的微任务,如果微任务又有宏任务和微任务,就再把其中的微任务执行完毕,把其中的宏任务放到宏任务队列中。
-
微任务队列清空后,到任务队列的宏任务中查找是否有任务,如果有就执行最开始添加的第一个宏任务,在执行宏任务时,如果有微任务出现就把这些微任务添加到新的微任务队列中。
-
第一个宏任务执行完毕后,就再去微任务队列中执行所有微任务。
-
步骤循环2-4直到任务队列全部清空。
代码测试
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout")
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1");
resolve()
}).then(function() {
console.log("promise2")
})
console.log("script end");
/*
运行顺序:
"script start"
"async1 start"
"async2"
"promise1"
"script end"
"async1 end"
"promise2"
"setTimeout"
*/