JavaScript中的事件循环机制
提示:微任务和宏任务是一种过时的说法了,据我所了解,已经没有宏任务的说法了
Js为什么是单线程的?
如果有三个按钮,其回调函数里面处理的代码量用的时间各不相同,用户依次快速的点击三个按钮,此时用户希望的是,先点击的先执行,后点击的后执行,可是由于是Js多线程的,每一个回调函数都会被注册成一个线程,这时,肯定是用时少的先执行。这就造成了一个很大的问题,用户体验很不好
可能有人会问,定时器里的代码为什没有按照顺序执行呢?
答:JavaScript采用了一种排队机制,将异步代码放入了消息队列中,待同步代码执行完后,如果消息队列中存在异步代码需要执行,就会唤醒Js主线程去执行这些异步代码
队列里的代码,谁先放进去,谁先执行。
Event Loop 图解
单线程也是存在着异步和同步的问题。这也正是我们今天的重点内容,事件循环机制(event loop)
先来一张图,感受一下
代码执行时,肯定是先执行同步代码,再执行异步代码,同步代码执行完后,其代码里定时器的回调函数,Dom事件的回调函数,Ajax请求的回调函数等这些异步代码,会由浏览器WebAPIs管理模块里的对应模块相应管理。当这些回调函数,到达要执行的条件(比如定时器到时间了,用户点击了),会由相应的模块送到队列里,然后执行。
先来看一段代码
setTimeout(() => {console.log('3')
},0)Promise.resolve(2).then(value => console.log(value)
)console.log('1')
控制台打印 1 2 3
你可能会很奇怪,定时器的第二个参数是0,即立马放进了队列里,Promise也是立马达到了满足条件。也会放进队列里,又根据代码的先后执行顺序,队列里肯定先放定时器的回调函数,再放Promise的回调函数,等同步代码执行完后,异步代码就是先执行定时器里回调函数,再执行Promise里的回调函数。控制台输出应该是1 3 2
才对啊!!!可是为啥是 1 2 3
呢?
这就引出了我们今天所学的第二个重点内容?宏任务与微任务
宏任务与微任务
其实,我们的上图是没有画完整的,下面来一张完整图,大家来感受一下
我们可以看出,队列里又进行了划分,又分为宏队列与微队列。
宏队列里放的是宏任务
微队列里放的是微任务
(注意:上图的标注的分线程是浏览器的,不是我们的Js代码的。我们只是写的代码交给了浏览器管理)
其Promise和Mutation(vue里会遇到mutation)里的回调函数,会放进微对列里
微队列优先级是高于宏队列优先级,所以上述代码,输出 1 2 3
就有理可寻啦
我们通过几个小题练习一下
setTimeout(() => {console.log('4')
},0)setTimeout(() => {console.log('5')
},0)Promise.resolve(2).then(value => console.log(value)
)Promise.resolve(3).then(value => console.log(value)
)console.log('1')//控制打印: 1 2 3 4 5
setTimeout(() => {console.log('4')
},0)Promise.resolve(2).then(value => console.log(value)
)Promise.resolve(3).then(value => console.log(value)
)console.log('1')//控制打印: 1 2 3 4
学到这里,你可能觉得已经结束了,其实并不,还有一个很重要的知识点没讲呢,我们接着学习
setTimeout(() => {console.log('4')
},0)setTimeout(() => {console.log('5')
},0)Promise.resolve(2).then(value => console.log(value)
)Promise.resolve(3).then(value => console.log(value)
)console.log('1')//控制打印: 1 2 3 4 5
setTimeout(() => {console.log('4')Promise.resolve(3).then(value => console.log(value))
},0)setTimeout(() => {console.log('5')
},0)Promise.resolve(2).then(value => console.log(value)
)console.log('1')
控制台打印1 2 4 3 5
别急,这里先给出结论:每执行一个宏任务时,都会检查微队列中是否有待执行的的回调,优先执行微任务
所以打印4后,将输出3的回调放进了微队列中,宏队列在执行输出5的回调函数前,先把微队列给清空了