为什么用node.js,node和浏览器的区别

  为什么用node.js,node和浏览器的区别

  本文将带您探索为什么浏览器和Node.js会这样设计EventLoop。希望对你有帮助!

  node.js速度课程简介:进入学习

  事件循环(Event Loop)是JavaScript的基本概念,在面试中是必不可少的,也是经常谈到的。但是你有没有想过为什么会有事件循环,为什么会这样设计?

  今天就来探讨一下原因。

  

浏览器的 Event Loop

   JavaScript用于实现网页的交互逻辑,涉及dom操作。如果多个线程同时运行,需要同步互斥,就设计成单线程来简化。但是,如果它是单线程的,它将被时序逻辑和网络请求阻塞。我该怎么办?

  您可以添加一层调度逻辑。将JS代码一个一个封装成任务,放在一个任务队列中,这样主线程就可以不断的取任务和执行任务。

  每次执行fetch任务时,都会创建一个新的调用堆栈。

  其中,定时器和网络请求实际上是由其他线程执行的。执行结束后,将一个任务放入任务队列,告诉主线程可以继续执行。

  因为这些异步任务是由其他线程执行,然后通过任务队列通知给下一个主线程,这是一种事件机制,所以这个循环叫做事件循环。

  由这些其他线程执行的异步任务包括计时器(setTimeout、setInterval)、UI呈现和网络请求(XHR或获取)。

  但是,当前事件循环有一个严重的问题。没有优先级的概念,只是按顺序执行。如果有高优先级的任务,就不会及时执行。所以,我们要设计一个插队的机制。

  那么有一个高优先级的任务队列就很好了。每次执行一个普通任务,先执行所有高优先级任务,然后再执行普通任务。

  有了插队机制,就能及时执行高质量的任务。

  这是现在浏览器的事件循环。

  普通任务称为宏任务,高级任务称为微任务。

  宏任务包括:setTimeout、setInterval、requestAnimationFrame、Ajax、fetch、script 标签的代码。

  微任务包括:Promise.then、MutationObserver、Object.observe。

  如何理解宏观和微观任务的划分?

  定时器,网络请求,是常见的异步逻辑,在其他线程运行后通知主线程,是宏任务。

  而这三种高质量的任务也很好理解。MutationObserver和Object.observe都监视一个对象的变化。变化是一个非常瞬间的事情,所以你必须立刻做出反应,否则可能会再次发生变化。Promise组织异步过程,在异步结束时调用then也是非常高质量的。

  这是浏览器中事件循环的设计:设计 Loop 机制和 Task 队列是为了支持异步,解决逻辑执行阻塞主线程的问题,设计 MicroTask 队列的插队机制是为了解决高优任务尽早执行的问题。

  不过后来JS的执行环境不仅仅是浏览器,还有Node.js,也要解决这些问题,但是它设计的事件循环更加细致。

  

Node.js 的 Event loop

  节点是一个新的JS运行时环境。它还必须支持异步逻辑,包括定时器、IO和网络请求。显然,它也可以用事件循环集来运行。

  然而,浏览器的事件循环集是为浏览器设计的,对于高性能服务器来说,这种设计还是有点粗糙。

  哪里粗糙?

  浏览器的事件循环只有两个优先级,一个是宏任务,一个是微任务。然而,在宏观任务和微观任务之间没有优先顺序。

  Node.js任务的宏任务也有优先级。比如Timer的逻辑比IO高,因为涉及到时间,越早越准确;但是关闭资源的处理逻辑优先级很低,因为不关闭最多占用更多的内存等资源,影响不大。

  因此,我们将宏任务队列分为五个优先级:定时器、挂起、轮询、检查、关闭。

  解释这五个宏观任务:

  Timers Callback:说到时间,越早执行越准确,所以很容易理解这个优先级是最高的。

  Pending Callback:处理网络、IO等异常时回调。有些*niux系统会等待错误报告,所以必须处理。

  Poll Callback:处理IO数据,网络连接,这是服务器主要处理的事情。

  Check Callback:执行setImmediate的回调。其特点是,您可以在执行IO后立即调用它。

  Close Callback:关闭资源的回调,后期执行的影响小于,优先级最低。

  Node.js的事件循环是这样运行的:

  还有一个区别需要特别注意:

  Node.js 的 Event Loop 并不是浏览器那种一次执行一个宏任务,然后执行所有的微任务,而是执行完一定数量的 Timers 宏任务,再去执行所有微任务,然后再执行一定数量的 Pending 的宏任务,然后再去执行所有微任务,剩余的 Poll、Check、Close 的宏任务也是这样。(修改:节点11之前是这样,节点11之后,改为每个宏任务执行所有微任务)

  这是为什么呢?

  实际上,就优先级而言,这很容易理解:

  假设浏览器中宏任务的优先级为1,那么它们是按顺序执行的,即一个宏任务,所有微任务,另一个宏任务,然后所有微任务。

  Node.js的宏任务也有优先级,所以Node.js 的 Event Loop 每次都是把当前优先级的所有宏任务跑完再去跑微任务,然后再跑下一个优先级的宏任务。

  即,一定数量的定时器宏任务,然后是所有的微任务,然后是一定数量的未决回调宏任务,然后是所有的微任务。

  为什么是一定数量?

  如果某个阶段的宏任务太多,下一个阶段就不能一直执行,所以有一个上限,剩下的下一个事件循环会继续执行。

  除了宏任务的优先级,微任务也有优先级,增加了一个process.nextTick的高优先级微任务,运行在所有普通微任务之前。

  所以,Node.js事件循环的完整过程是这样的:

  Timers 阶段:执行一定数量的定时器,即setTimeout和setInterval的回调。如果太多,留到下一个微任务:执行nextTick的所有微任务,然后是其他普通微任务。Pending 阶段:执行一定数量的IO和网络异常回调,保存过多的微任务供下次使用:执行nextTick的所有微任务,然后执行其他普通微任务。Idle/Prepare 阶段:内部使用的一阶段微任务:执行nextTick的所有微任务,然后执行其他普通微任务。Poll 阶段:执行一定数量文件的数据回调和网络的连接回调,留太多下次用。如果没有 IO 回调并且也没有 timers、check 阶段的回调要处理,就阻塞在这里等待 IO 事件微任务:执行nextTick的所有微任务,然后执行其他普通微任务。Check 阶段:执行一定数量的setImmediate的回调,留太多下次用。微任务:执行nextTick的所有微任务,然后执行其他普通微任务Close 阶段:执行一定数量的close事件回调,太多留到下次。微任务:执行nextTick的所有微任务,然后再执行其他普通微任务,显然比浏览器中的事件循环要复杂得多,但是经过我们之前的分析,我们也可以理解:

  Node.js 对宏任务做了优先级划分,从高到低分别是 Timers、Pending、Poll、Check、Close 这 5 种,也对微任务做了划分,也就是 nextTick 的微任务和其他微任务。执行流程是先执行完当前优先级的一定数量的宏任务(剩下的留到下次循环),然后执行 process.nextTick 的微任务,再执行普通微任务,之后再执行下个优先级的一定数量的宏任务。。这样不断循环。其中还有一个 Idle/Prepare 阶段是给 Node.js 内部逻辑用的,不需要关心。

  改变浏览器事件循环中一次执行一个宏任务的方式,可以使优先级高的宏任务更早执行,但也设置了上限,防止下一阶段一直执行。

  另外特别需要注意的一点是民调阶段:如果执行到 poll 阶段,发现 poll 队列为空并且 timers 队列、check 队列都没有任务要执行,那么就阻塞的等在这里等 IO 事件,而不是空转。。这一点也是因为服务器主要处理IO而设计的,拥塞在这里可以更早的响应IO。

  完整Node.js的事件循环是这样的:

  比较下浏览器的事件循环:

  两个JS运行时环境的事件循环整体设计思路是相似的,只是Node.js的事件循环对宏观任务和微观任务做了更细粒度的划分,很容易理解。毕竟Node.js面对的环境和浏览器不一样,更重要的是服务器端会有更高的性能要求。

  

总结

   JavaScript最早用于编写网页的交互逻辑。为了避免多线程同时修改dom的同步问题,将其设计为单线程。为了解决单线程的阻塞问题,增加了一层调度逻辑,即Loop loop循环和任务队列,将阻塞的逻辑放在其他线程中,从而支持异步。然后为了支持高优先级的任务调度,引入了微任务队列,微任务队列为浏览器的 Event Loop 机制:每次执行一个宏任务,然后执行所有微任务。

  Node.js也是一个js运行环境。如果要支持异步,也应该使用事件循环,但是服务器端环境更复杂,对性能要求更高。所以Node.js对宏观和微观任务做了更细粒度的优先级划分:

  Node.js中有五个宏任务,分别是Timers、Pending、Poll、Check和Close。微任务有两种,分别是process.nextTick的微任务和其他微任务。

  Node.js 的 Event Loop 流程是执行当前阶段的一定数量的宏任务(剩余的到下个循环执行),然后执行所有微任务,一共有 Timers、Pending、Idle/Prepare、Poll、Check、Close 6 个阶段。(修改:节点11之前是这样,节点11之后,改为每个宏任务执行所有微任务)

  空闲/准备阶段由Node.js内部使用,所以不用担心。

  特别要注意的是 Poll 阶段,如果执行到这里,poll 队列为空并且 timers、check 队列也为空,就一直阻塞在这里等待 IO,直到 timers、check 队列有回调再继续 loop。

  事件循环是JS设计的一组调度逻辑,用于支持异步和任务优先级。针对浏览器、Node.js等不同环境有不同的设计(主要是任务优先级划分的粒度不同)。Node.js面对的环境更复杂,对性能要求更高,所以事件循环的设计也更复杂。

  更多关于node的信息,请访问:nodejs教程!以上是探讨浏览器和Node.js为什么要这样设计EventLoop!更多详情请关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: