当前位置: 首页 > 后端技术 > Node.js

浏览器与Node的事件循环(EventLoop)不同

时间:2023-04-03 21:11:39 Node.js

注意,在node11版本中,node下的EventLoop已经趋向于与浏览器相同。在node11版本中,node下的EventLoop变得和浏览器一样了。在node11版本中,node下的EventLoop变得和浏览器一样了。后台事件循环也是js中的一个常见话题。看了阮一峰老师二月底的文章《Node定时器详解》,发现自己不能完全匹配之前看的js事件循环的执行机制。我还查阅了一些其他资料,并记录下来作为笔记。觉得不妥,总结成一篇文章。浏览器中的事件循环和执行机制与node中的不同,不要混淆。浏览器的事件循环是HTML5中定义的规范,而它是由node.js中的libuv库实现的。同时,在看《深入浅出nodeJs》这本书的时候,发现当时的节点机制不一样,所以本文的节点部分以本文发表时的版本为准。强烈建议阅读参考链接中的前三篇文章。浏览器环境下JS的执行是单线程的(不考虑webworker),所有代码都在执行线程的调用栈上执行。当执行线程任务清空时,它会轮询任务队列中的任务。任务队列异步任务分为task(宏任务,也叫macroTask)和microtask(微任务)两种。当满足执行条件时,task和microtask会被放入各自的队列中,等待放入执行线程中执行。我们称这两个队列为TaskQueue(也叫MacrotaskQueue)和MicrotaskQueue。任务:脚本中的代码、setTimeout、setInterval、I/O、UI渲染。微任务:承诺,对象。观察,MutationObserver。具体进程执行主执行线程中的任务。取出MicrotaskQueue中的任务执行,直到清空。从MacrotaskQueue中取出一个任务执行。取出MicrotaskQueue中的任务执行,直到清空。重复3和4。即同步完成,一个宏任务,所有微任务,一个宏任务,所有微任务...注意,在浏览器页面中,可以认为在初始执行时没有代码thread,而每个script标签中的代码是一个独立的task,会执行前面脚本中创建的microtask,然后执行后面脚本中的同步代码。如果添加了microtask,它会继续执行microtask并“卡住”macrotask。部分版本浏览器执行顺序与上述不符,可能是不符合标准或js与html标准冲突。您可以阅读参考文章中的第一篇文章。newPromise((resolve,reject)=>{console.log('synchronous');resolve()}).then(()=>{console.log('asynchronous')}),即thenandcatchofpromise是微任务,而不是内部代码本身。未列出个别浏览器特定的API。伪代码while(true){宏任务queue.shift()微任务队列所有任务()}node环境js单线程执行,所有代码都在执行线程的调用栈中执行。当执行线程任务清空时,它会轮询任务队列中的任务。在循环阶段,node中事件的每个循环依次分为6个阶段,从libuv的实现开始:定时器:执行满足条件的setTimeout和setInterval回调。I/Ocallbacks:完成的I/O操作是否有回调函数,是上一轮轮询遗留下来的。idle、prepare:可以忽略poll:等待还没有完成的I/O事件,会因为定时器和超时而结束等待。check:执行setImmediate的回调。关闭回调:关闭所有关闭句柄,一些onclose事件。执行机制Severalqueues除了上述循环阶段的任务类型,我们还有浏览器和节点共享的microtask和节点独有的process.nextTick,我们称之为MicrotaskQueue和NextTickQueue。我们也将循环中几个阶段的执行队列称为TimersQueue、I/OQueue、CheckQueue和CloseQueue。在循环进入第一次循环之前,首先会进行如下操作:同步任务发送异步请求,规划定时器生效的时间,执行process.nextTick()开始循环。按照我们循环的6个阶段依次执行,每次取出当前阶段的所有任务执行完毕,清空NextTickQueue,清空MicrotaskQueue。然后执行下一阶段。6个阶段全部执行完后,进入下一个循环。即:清空当前循环中的TimersQueue,清空NextTickQueue,清空MicrotaskQueue。清空当前循环中的I/OQueue,清空NextTickQueue,清空MicrotaskQueue。清空当前周期的CheckQueue,清空NextTickQueue,清空MicrotaskQueue。清空当前周期的CloseQueue,清空NextTickQueue,清空MicrotaskQueue。进入下一个循环。可以看出nextTick的优先级高于promise等微任务。setTimeout和setInterval的优先级高于setImmediate。注意,如果setImmediate是在timers阶段执行时创建的,那么会在本次循环的check阶段执行。如果setTimeout是在timers阶段创建的,由于timers已经取出来了,就会进入下一个循环。在检查阶段创建定时器任务也是如此。setTimeout的优先级高于setImmediate,但是由于setTimeout(fn,0)的真实延迟不可能完全为0秒,所以可能会出现先创建的setTimeout(fn,0)在setImmediate回调之后执行的情况。伪代码while(true){loop.forEach((stage)=>{stagealltasks()nextTickalltasks()microTaskalltasks()})loop=loop.next}测试代码functionsleep(time){letstartTime=newDate()while(newDate()-startTime{console.log('setTimeout-1')setTimeout(()=>{console.log('setTimeout-1-1')sleep(1000)})newPromise(resolve=>resolve()).then(()=>{console.log('setTimeout-1-then')newPromise(resolve=>resolve()).then(()=>{console.log('setTimeout-1-then-then')})})sleep(1000)})setTimeout(()=>{console.log('setTimeout-2')setTimeout(()=>{console.log('setTimeout-2-1')sleep(1000)})newPromise(resolve=>resolve()).then(()=>{console.log('setTimeout-2-then')newPromise(resolve=>resolve()).then(()=>{console.log('setTimeout-2-then-then')})})sleep(1000)})浏览器输出:setTimeout-1//1对于单个任务1soversetTimeout-1-thensetTimeout-1-然后-然后setTimeout-2//2对于单个任务1soversetTimeout-2-然后setTimeout-2-then-thensetTimeout-1-11soversetTimeout-2-11sovernode输出:setTimeout-11soversetTimeout-2//1和2是单阶段task1soversetTimeout-1-thensetTimeout-2-thensetTimeout-1-then-thensetTimeout-2-then-thensetTimeout-1-11soversetTimeout-2-11sover也可以看出事件循环在浏览器和节点上是不同的。强烈建议参考文章Tasks,microtasks,queuesandschedules,不要混淆nodejs和浏览器中的事件循环。强烈推荐node中的Event模块强烈推荐了解事件循环1(解析)NodeTimer详解???