什么是事件循环?用来解决什么?
众所周知,JS是单线程的,但与其他主流编程语言一样,JS中也有异步的概念。其他语言中异步任务会利用自身的多线程新开一个线程去执行异步任务,而不会阻塞主线程中的任务,但在JS中如果直接执行异步任务就会阻塞这个线程(没办法,JS只有一个线程),所以事件循环的设计就是为了解决线程阻塞的问题,将异步任务收集起来集中处理(先将同步任务执行完毕)
事件循环的过程
JS执行代码为从上到下一行一行的执行,当遇到同步任务时,会交给主线程直接处理,遇到异步任务时则会将其挂起到一个任务队列中。
当处理完所有同步任务之后,则会从任务队列中读取任务,向上面那样执行此任务(如果为空则一直读取,直到里面有任务)
任务队列分为宏任务与微任务
执行顺序如下:
- 取出一个宏任务(最初整个JS代码块即为一个宏任务),开始执行
- 遇到异步任务,根据任务类型将其加入宏任务队列或微任务队列
- 此任务执行完毕后,从微任务队列出队一个微任务进行执行
2
(若为空则进行4
) - 微任务队列清空后,执行渲染,如果宏任务队列不为空则进入
1
,开启新一轮的时间循环(如果为空,则重复检查宏任务队列)
记得画个图…
微任务与宏任务
- 宏任务:宿主(浏览器|node)发起
- script整体代码
- setTimeout、setInterval、serImmediate(node方法)
- I/O事件
- UI rendering
- 微任务:js引擎发起
- Promise的then、catch、finally执行
- process.nextTick() (nodejs-可保证方法在对象完成constructor后但是在IO发生前调用)
- async\await(本质上是Promise)
- MutationObserver
为什么要区分呢?我想是为了效率,将所有微任务执行完后的状态进行渲染,要好过执行一个异步任务就渲染一次
示例
console.log('script start')
async function async1(){
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2 end')
}
async1()
setTimeout(function(){
console.log('setTimeout')
},0)
new Promise(resolve => {
console.log('Promise')
resolve()
}).then(function(){
console.log('promise1')
}).then(function(){
console.log('promise2')
})
console.log('script end')
执行顺序如下:
- 遇到console.log,输出
script start
- 遇到async1(),开始执行:
- await async2():await可以理解为隐式调用then,async2()直接执行(相当于new Promise()传入的函数)
- async2中输出
async2 end
- await注册一个微任务,跳出此函数
- 执行setTimeout,注册一个宏任务
- 执行new Promise,输出
Promise
、then()注册一个微任务 - 执行console.log,输出
script end
- 当前微任务队列为【await async2】【promise.then()】,所以await后输出
async1 end
,then()里输出promise1
,在输出promise1后又注册一个then()随后执行输出promise2
- 微任务队列清空,执行宏任务【setTimeout】,输出
setTimeout
关于async/await
async function async1(){
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2 end')
}
async1()
// 上面的代码中关于async await可解释为Promise理解
function async1(){
return new Promise((resolve, reject) => {
//before await ...
// start await
let res // res用于接收await返回值(上面并没有接收await的返回值,这里写出来只是方便理解)
async2().then(r => { // [1]
// await 返回
res = r;
// await之后
console.log('async1 end')
resolve()
})
})
}
function async2(){
return new Promise((resolve, reject) => {
//before async2 return
console.log('async2 end')
resolve()
})
}
// 如此结合上面的示例代码后,输出顺序就是
// script start
// async2 end -- 微任务加入[1]
// Promise -- 微任务加入示例中的Promise.then,记为[2]
// script end
// async1 end -- [1]执行的结果
// promise1 -- [2]执行的结果,并加入后一个then记为[3]
// promise2 -- [3]执行的结果
// setTimeout -- 宏任务执行结果
- PS: 在async async2函数中返回一个Promise时,await处会比返回一个普通值要多触发两次微任务(理论上应该只触发一次——将Promise中的值取出,但不知道处于什么考虑做了两次微任务,上述示例中的async1 end就会延迟到promise2之后才会执行)
node中的事件循环
… TODO