为什么需要事件循环
-
JS是单线程的语言
单线程就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,会存在的问题是:当js引擎遇到耗时较多的任务时,如果一直等待其结果会导致大量的资源浪费,于是开发者将任务分为两类:同步任务和异步任务
什么是事件循环
- js引擎遇见一个异步操作时,并不会等待其事件的返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。
- 当异步操作返回结果后,js会将事件加入当前执行栈中的事件队列中,并不马上执行事件队列中的回调,而是等待当前执行栈中的所有任务都执行完毕
- 主线程处于闲置状态时,主线程会去事件队列中查找是否有任务,如果有,那么主线程会取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其同步代码...
- 这样一个反复的过程就是事件循环
异步任务又分为宏任务和微任务
首先我们看一个例子
console.log('学习start')
setTimeout(()=>{
console.log('学习setTimeout')
},0)
new Promise(function(resolve) {
console.log('学习promise start');
resolve();
}).then(function() {
console.log('学习promise end')
})
console.log('学习end')
//执行顺序:学习start-学习promise start-学习end-学习promise end-学习setTimeout
我们已经知道同步代码先于异步代码执行,但是为什么.then优先setTimeout执行呢?我们不得不讲到宏任务和微任务
- 宏任务包括:整体代码script,setTimeout,setInterval、setImmediate。
- 微任务包括:promise.[ then/catch/finally ]((非new Promise)),process.nextTick(Node.js 环境),MutaionOberver(浏览器环境),Object.observe
- 宏任务优先于微任务执行。进入一个宏任务,先执行其同步代码,遇到宏任务和微任务分别将其放入宏任务的事件队列和微任务的事件队列,当同步代码执行完毕后,再取出本次宏任务中的微任务的回调执行完毕,直到本次宏任务中的微任务全部执行完毕,再进入一个新的宏任务...这样循环
什么是宏任务
宿主环境提供的,比如浏览器
ajax、setTimeout、setInterval、setTmmediate(只兼容ie)、script、requestAnimationFrame、messageChannel、UI渲染、一些浏览器api
什么是微任务
语言本身提供的,比如promise.then
then、queueMicrotask(基于then)、mutationObserver(浏览器提供)、messageChannel 、mutationObersve
微任务的出现其实就是语言设计中的一种实时性和效率的权衡体现。当宏任务执行时间太久,就会影响到后续任务的执行,而此时因为某些需求,编程人员需要让某些任务在宿主环境(比如浏览器)提供的事件循环下一轮执行前执行完毕,提高实时性,这就是微任务存在的意义
再看一个例子
async function async1() {
console.log('async1 start');
await async2();
console.log('asnyc1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
console.log('promise1');
reslove();
}).then(function () {
console.log('promise2');
})
console.log('script end');
答案
// 由于script本身是一个宏任务,先执行其同步代码
1.script start
// 遇到setTimeout将其放入宏任务事件队列中
// 执行async1();
2.async1 start
// await async2(); 执行async2()
3.async2
// await让出线程,先执行async外部的代码
// new Promise()
4.promise1
// 遇到reslove(),将其加入微任务的事件队列中
5.script end
// 同步代码执行完毕,执行awaitd额后续代码
6.async1 end
// 微任务队列中的.then
7.promise2
// 执行宏任务中的代码
8.setTimeOut