当前位置: 首页 > 科技观察

什么是JS中的事件循环?

时间:2023-03-20 11:16:44 科技观察

大家好,我是前端西瓜哥,今天来学习什么是EventLoop。EventLoop,简单翻译就是事件循环,是一种用JS语言实现runtime的机制。JS的异步并没有像其他语言(比如Java)的异步那样实现真正的并发执行。它实际上是一个单线程。JS维护了一个任务队列,每当一些异步任务要执行的时候,比如定时器或者点击按钮触发的事件响应函数。它们不会立即执行,而是放在这个队列中,等待其他已经在队列中的任务执行完才轮到它们。队列是具有有限操作的有序列表。表明先进入的元素必须先出去,即“先进先出”,很像排队的感觉。但是也有一些特殊的队列,比如优先级队列,优先级高的元素先出队。之所以叫EventLoop,是因为它的逻辑可以描述为如下代码:下一个任务状态,然后处理下一个任务,依此类推。因为JS本身的代码执行是单线程的,为了不阻塞执行,JS会将网络请求操作、渲染浏览器页面等操作交给其他线程,等待其他线程处理并返回结果到JS。所以JS不适合CPU密集型,更适合IO密集型场景。因为它只有一个线程,如果计算时间过长,会阻塞其他任务的执行,造成卡顿甚至崩溃。setTimeout定时器不允许setTimeout函数在特定时间后执行,不会立即执行。而是先放到任务队列中,等待前面的任务同步执行完成后,我们才会执行这个。我们看一个例子,因为第一个setTimout有一个非常耗时的同步任务,导致下一个setTimeout的执行阻塞,比上一个setTimeout的执行慢了半秒。conststart=newDate().getTime();setTimeout(()=>{console.log('1:',newDate().getTime()-start);letnum=0;for(leti=0;i<999999999;i++){num=i;}},1000);setTimeout(()=>{console.log('2:',newDate().getTime()-start);},1000);/***输出结果:*1:1001*2:1505*/定时器的时间是指最早可以执行的时间,但不能保证在这个时间点立即执行。宏任务和微任务的任务队列不是严格先进先出的普通队列,执行顺序可以调整。我们将要执行的任务分为宏任务和微任务。其中,宏任务是正常的先进先出,而微任务可以在宏任务之前先排队执行。宏任务必须在所有微任务执行完后执行。当我们向任务队列中添加一个微任务时,它会在任务队列宏任务之前运行。当多个微任务入队时,它们的相对顺序会保持不变。宏任务包括:脚本,即嵌入在HTML中的脚本。setTimeout/setInterval计时器。setImmediate,这是一个特定于nodejs的API。requestAnimationFrame将在页面重绘之前执行。I/O操作,比如网络请求完成的回调函数执行任务,点击按钮时要执行的回调等。这些操作实际上是在其他线程完成后触发的,总结为I/O操作暂时。Microtasks包括:当Promise从pending状态转换到其他状态时,then/catch/finaly中的函数被触发,比如Promise.resolve().then(fn)。这是最常见的微任务。MutationObserver,用于监控DOM变化。process.nextTick,nodejs特定的API。理论上一个任务队列就够了,但也可以是多个队列的组合,没有强制要求。多任务队列的实现可以更好的实现优先级控制。比如定时器任务,理论上应该在多个宏任务中最先执行。浏览器不考虑这种情况,但是nodejs也给宏任务设置了优先级,这样定时器任务会先执行。EventLoop还是比较复杂的,标准文档也比较长。我读得不多。如果你有兴趣,你可以看看。https://html.spec.whatwg.org/multipage/webappapis.html#event-loops。一个经典的异步问题asyncfunctionasync1(){console.log("async1start");等待async2();console.log("async1end");}asyncfunctionasync2(){console.log("async2");}console.log("scriptstart");setTimeout(function(){console.log("setTimeout");},0);async1();newPromise(function(resolve){console.log("promise1");resolve();}).then(function(){console.log("promise2");});console.log('scriptend')解决问题的思路是:找到同步代码。同步代码包括:普通的同步代码,newPromise(fn)执行传入的回调函数,async执行时遇到await前面部分(包括await右边函数的执行也是同步的,这是一个容易出错的点)。查看任务队列中的微任务和宏任务,记住宏任务只有在所有微任务执行完毕后才会执行。执行任务,任务中的异步任务依次进入任务队列。结果是://同步代码scriptstartasync1startasync2promise1scriptend//microtaskasync1endpromise2//macrotasksetTimeoutendJS运行机制是单线程的,当有多个异步任务同时执行时,它们不能并发执行,而且优先级一定要高任务执行完之后,下面的就可以执行了。如果正在执行的任务比较耗时,会导致后续任务被阻塞。在EventLoop的机制中,最基本的是:microtasks先于macrotasks执行。