什么是事件循环?
JavaScript是一门单线程语言,即当有一个任务在执行的时候,其他任务需要在后面等待。但在实际场景中,有些任务可以放在比较靠后的位置,比如加载网络资源时发送的异步请求。
事件循环就是JavaScript异步执行机制的一种实现方式。
JavaScript执行机制
前面说到执行上下文存在创建阶段和执行阶段,这一章节主要针对执行阶段来讲。即在分析JavaScript代码执行时,首先要做一次执行上下文创建阶段的分析,而后才能利用这篇文章的知识点来分析JavaScript的执行机制。
我们常说JavaScript是按照代码顺序一行行执行下来的,但这段代码实际输出的顺序是2,1。这是为什么呢?
setTimeout(function(){
console.log(1);
},0)
console.log(2);
JavaScript引擎会智能地将任务分成同步任务和异步任务。
- 同步任务:主代码
- 异步任务:setTimeout、Promise、setInterval、nextTick等等
首先JavaScript引擎会将整体script代码放入执行栈中,遇到同步任务则直接进入主线程执行,遇到异步任务时注册回调函数,并将异步任务放进事件队列中等待。
待主线程中的所有同步任务执行完毕后,才会去事件队列处取任务。根据队列先进先出的特点,最先进入事件队列的异步任务会被率先执行。
而在实际场景中,并不是从事件队列一一取出异步任务直接执行这么简单。从宏观定义上,我们将JavaScript的任务分为同步任务和异步任务,但在事件循环中,更加精细的分法是将任务分为宏任务和微任务。不同类型的任务会进入不同的事件队列中。
- 宏任务:一般的JavaScript同步代码,定时器相关的异步代码setTimeout、setInterval
- 微任务:Promise、nextTick等
JavaScript引擎执行代码过程
在JavaScript引擎执行代码的过程中,首先将整体代码作为一个宏任务执行,遇到一般的同步代码立即执行,遇到定时器相关如setTimeout这种宏任务代码则先将其放入到宏任务事件队列中,遇到Promise这种则将其catch、then函数放入到微任务队列中。
整体代码作为第一次宏任务执行完毕后,JavaScript引擎会先到微任务队列中看看有没有任务,如有任务则将其全部执行,如果没有则进入到下一次宏任务中。也就是说JavaScript执行机制是按照宏任务-微任务-宏任务-微任务...这样子循环执行的。
根据队列先进先出的特点,取出宏任务事件队列中第一个宏任务率先执行,遇到同步代码立即执行,遇到微任务则继续进入微任务事件队列。待这一次的宏任务执行完毕后,则继续执行所有的微任务。如此循环下去,则就是事件循环。
先举个简单的例子
setTimeout(function(){
console.log(0);
},3000);
new Promise((resolve,reject)=>{
console.log(2);
resolve();
}).then(()=>{
console.log('resolve');
}
);
console.log(1)
第一遍事件循环过程:
- 依据上述的结论,整体代码作为宏任务首先进入主线程中执行,setTimeout是一个宏任务,则先将其放入到宏任务事件队列中。
- 继续下面的代码,遇到Promise里面的立即执行语句,则直接输出2。遇到then函数将其放入到微任务事件队列中,遇到最后一句输出1。
- 在这一遍宏任务执行过程中,宏任务事件队列中有一个setTimeout在等待执行,微任务事件队列中有一个then函数在等待执行。
- 则依据宏任务-微任务-宏任务-微任务的执行特点,接下来要去微任务事件队列中取任务,则跟着要输出then函数中的resolve。微任务事件队列仅有这一个微任务,至此微任务全部执行完毕。第一遍事件循环过程结束。
第二遍事件循环过程:
- 第二遍事件循环仍旧从宏任务开始,要去宏任务事件队列中取一个宏任务执行,此时第一个宏任务是setTimeout,则直接执行输出0即可。
- 再去微任务事件队列中查看是否还有微任务等待执行,发现微任务事件队列中没有任务。则跳过,继续去宏任务事件队列中查看是否有宏任务,依旧没有任务。至此事件循环结束。这段代码输出的顺序是2,1,resolve,0。
上面给定时器的延迟时间是3秒钟,有无可能是时间片竞争的机制抢着输出?试着把setTimeout的延迟时间改为0。
setTimeout(function(){
console.log(0);
},0);
new Promise((resolve,reject)=>{
console.log(2);
resolve();
}).then(()=>{
console.log('resolve');
}
);
console.log(1)
发现输出的顺序一致,实际上将延迟设置为0,也不是立即执行。
依据上面的结论,将setTimeout的延迟时间设为0的意思是待主线程空闲后便可以执行,还得等主线程按照宏任务-微任务-宏任务-微任务的事件循环顺序执行完毕后。实际上即使主线程空闲了,延迟执行也不会达到0秒立即执行,最低也是4ms。
小结
- 同步任务进入主线程率先执行,异步任务注册回调函数后进入事件队列
- 宏任务:大部分JavaScript同步代码,定时器相关
setTimeout
、setInterval
微任务:Promise
、nextTick
- JavaScript执行机制按照宏任务-微任务-宏任务-微任务的循环顺序执行。注意在每轮事件循环过程中,执行宏任务是从宏任务事件队列中取出最早进入的一个任务,而微任务则是全部执行完毕。
参考文章:https://juejin.im/post/59e85e...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。