什么是事件循环?
JavaScript是一门单线程语言,即当有一个任务在执行的时候,其他任务需要在其后方等待。而在实际场景中,有些任务可以放在比较靠后的位置,比如加载网络资源时候发送的异步请求。总而言之,事件循环就是JavaScript异步执行机制的一种实现方式。
我们经常性地认知是,JavaScript是按照顺序从上往下执行的,但是看如下代码:
setTimeout(()=>{ console.log(111)},0) cosole.log(222)
实际上真实操作的话是输出顺序为222,111。这是为什么呢?
Javascript引擎会智能地将任务分布为同步任务与异步任务。同步任务地话:主代码。异步任务的话,如:setTimeout、Promise、setInterval、nextTick等。
首先Javascript引擎会将整体script代码放入执行栈中,遇到同步任务则直接进入主线程执行,遇到异步任务时注册回调函数,并将异步任务放入事件队列中等待。
待主线程中的所有同步任务执行完毕后,才会去事件队列处取任务。根据队列先进先出的特点,最先进入队列的异步任务会被率先执行。
而在实际场景中,并不是从事件队列中挨个取出异步任务直接执行那么简单。从宏观意义上,我们将JavaScript的任务分为同步任务与异步任务,但在事件循环中,我们将其分为了宏任务与微任务。不同类型的任务会进入不同的事件队列中。
宏任务:一般的JavaScript同步代码,定时器相关的异步代码setTimeout、setInterval。
微任务:promise、nextTick等。
在Javascript引擎执行代码的过程当中,首先将整体代码作为一个宏任务执行,遇到一般的同步代码立即执行,遇到定时器相关如setTimeout这种宏任务,则先将其放入到宏任务事件队列中,遇到Promise这种则将其catch、then函数放到微任务队列中。整体代码,作为第一次宏任务执行完毕后,JavaScript引擎会先到微任务队列中看看有没有任务,如果有的话,就会将其全部执行完毕;如果没有的话,则会进入到下一次宏任务中。也就是说,JavaScript执行机制是按照宏任务-微任务-宏任务-微任务..这样子循环执行。
根据队列先进先出的特点,取出宏任务事件队列中第一个宏任务会先执行,遇到同步代码立即执行,遇到微任务则继续进入微任务事件队列。待这一次的宏任务执行完毕后,则继续执行所有微任务。如此循环下去,即为事件循环。
setTimeout(()=>{ console.log(111)},1000); new Promise((resolve,reject) =>{ console.log(222); resolve();}).then(() =>{ console.log(333);}); console.log(444)
我们可以把上面的代码看成两次事件循环。
我们将整体代码作为宏任务让其进入主线程中执行,由于setTimeout事件为一个宏任务,因此我们把它放在宏任务事件队列当中。往下执行的时候,遇到promise里面的立即执行语句,因此我们直接输出222;遇到then函数,我们将其放入到微任务事件队列当中,放在较为后面的顺序。后期还有个444,直接执行输出。接着我们按照宏任务-微任务-宏任务的顺序,可知下一步执行的即为微任务,promise中的then函数,则输出333,最后再输出setTimeout中的111。
因此顺序为:222、444、333、111.
注意:即使setTimeout函数的延迟时间设置为0,也不会立即执行。这边将setTimeout的延迟时间设置为0的意思是待主线程空闲后便可执行。因此还是得等到主线程按照宏-微-宏这样的事件循环顺序执行下去。
既然说到了setTimeout与setInterval,那么我们来复习一下这两个。
JS中定时器与延时调用
1.定时器:规定过多长时间执行一次。
setInterval(() =>{
console.log(aaa);},1000)
这句话的意思为,每隔一秒输出一个aaa,可以执行多次。
2.延时调用:这个函数不会马上执行,而是过了多久再执行。
setTimeout(()=>{ console.log(bbb) },1000)
这句的意思是过一秒再输出bbb,而且只执行一次。
注意,在每次setInterval之前都要进行一次clearInterval(timer),来把上一次的定时器清除掉,这样就不会使得动画的速度越来越快。如果不使用clearInterval,那么每执行一次,数值都会叠加一次,则会导致越来越快。并且在执行到所设置的边界条件之后,也要使用clearInterval一下让本次的这个动画停掉。