资讯专栏INFORMATION COLUMN

浏览器和Node不同的事件循环(Event Loop)

haitiancoder / 1057人阅读

摘要:浏览器中与中事件循环与执行机制不同,不可混为一谈。浏览器环境执行为单线程不考虑,所有代码皆在执行线程调用栈完成执行。参考文章强烈推荐不要混淆和浏览器中的强烈推荐中的模块强烈推荐理解事件循环一浅析定时器详解

注意
在 node 11 版本中,node 下 Event Loop 已经与浏览器趋于相同。
在 node 11 版本中,node 下 Event Loop 已经与浏览器趋于相同。
在 node 11 版本中,node 下 Event Loop 已经与浏览器趋于相同。
背景

Event Loop也是js老生常谈的一个话题了。2月底看了阮一峰老师的《Node定时器详解》一文后,发现无法完全对标之前看过的js事件循环执行机制,又查阅了一些其他资料,记为笔记,感觉不妥,总结成文。

浏览器中与node中事件循环与执行机制不同,不可混为一谈。
浏览器的Event loop是在HTML5中定义的规范,而node中则由libuv库实现。同时阅读《深入浅出nodeJs》一书时发现比较当时node机制已有不同,所以本文node部分针对为此文发布时版本。强烈推荐读下参考链接中的前三篇。

浏览器环境

js执行为单线程(不考虑web worker),所有代码皆在执行线程调用栈完成执行。当执行线程任务清空后才会去轮询取任务队列中任务。

任务队列

异步任务分为task(宏任务,也可称为macroTask)和microtask(微任务)两类。
当满足执行条件时,task和microtask会被放入各自的队列中等待放入执行线程执行,我们把这两个队列称为Task Queue(也叫Macrotask Queue)和Microtask Queue。

task:script中代码、setTimeout、setInterval、I/O、UI render。

microtask: promise、Object.observe、MutationObserver。

具体过程

执行完主执行线程中的任务。

取出Microtask Queue中任务执行直到清空。

取出Macrotask Queue中一个任务执行。

取出Microtask Queue中任务执行直到清空。

重复3和4。

即为同步完成,一个宏任务,所有微任务,一个宏任务,所有微任务......

注意

在浏览器页面中可以认为初始执行线程中没有代码,每一个script标签中的代码是一个独立的task,即会执行完前面的script中创建的microtask再执行后面的script中的同步代码。

如果microtask一直被添加,则会继续执行microtask,“卡死”macrotask。

部分版本浏览器有执行顺序与上述不符的情况,可能是不符合标准或js与html部分标准冲突。可阅读参考文章中第一篇。

new Promise((resolve, reject) =>{console.log(‘同步’);resolve()}).then(() => {console.log("异步")}),即promisethencatch才是microtask,本身的内部代码不是。

个别浏览器独有API未列出。

伪代码
while (true) {
  宏任务队列.shift()
  微任务队列全部任务()
}
node环境

js执行为单线程,所有代码皆在执行线程调用栈完成执行。当执行线程任务清空后才会去轮询取任务队列中任务。

循环阶段

在node中事件每一轮循环按照顺序分为6个阶段,来自libuv的实现:

timers:执行满足条件的setTimeout、setInterval回调。

I/O callbacks:是否有已完成的I/O操作的回调函数,来自上一轮的poll残留。

idle,prepare:可忽略

poll:等待还没完成的I/O事件,会因timers和超时时间等结束等待。

check:执行setImmediate的回调。

close callbacks:关闭所有的closing handles,一些onclose事件。

执行机制 几个队列

除上述循环阶段中的任务类型,我们还剩下浏览器和node共有的microtask和node独有的process.nextTick,我们称之为Microtask Queue和NextTick Queue。

我们把循环中的几个阶段的执行队列也分别称为Timers Queue、I/O Queue、Check Queue、Close Queue。

循环之前

在进入第一次循环之前,会先进行如下操作:

同步任务

发出异步请求

规划定时器生效的时间

执行process.nextTick()

开始循环

按照我们的循环的6个阶段依次执行,每次拿出当前阶段中的全部任务执行,清空NextTick Queue,清空Microtask Queue。再执行下一阶段,全部6个阶段执行完毕后,进入下轮循环。即:

清空当前循环内的Timers Queue,清空NextTick Queue,清空Microtask Queue。

清空当前循环内的I/O Queue,清空NextTick Queue,清空Microtask Queue。

清空当前循环内的Check Queu,清空NextTick Queue,清空Microtask Queue。

清空当前循环内的Close Queu,清空NextTick Queue,清空Microtask Queue。

进入下轮循环。

可以看出,nextTick优先级比promise等microtask高。setTimeoutsetInterval优先级比setImmediate高。

注意

如果在timers阶段执行时创建了setImmediate则会在此轮循环的check阶段执行,如果在timers阶段创建了setTimeout,由于timers已取出完毕,则会进入下轮循环,check阶段创建timers任务同理。

setTimeout优先级比setImmediate高,但是由于setTimeout(fn,0)的真正延迟不可能完全为0秒,可能出现先创建的setTimeout(fn,0)而比setImmediate的回调后执行的情况。

伪代码
while (true) {
  loop.forEach((阶段) => {
    阶段全部任务()
    nextTick全部任务()
    microTask全部任务()
  })
  loop = loop.next
}
测试代码
function sleep(time) {
  let startTime = new Date()
  while (new Date() - startTime < time) {}
  console.log("1s over")
}
setTimeout(() => {
  console.log("setTimeout - 1")
  setTimeout(() => {
      console.log("setTimeout - 1 - 1")
      sleep(1000)
  })
  new Promise(resolve => resolve()).then(() => {
      console.log("setTimeout - 1 - then")
      new Promise(resolve => resolve()).then(() => {
          console.log("setTimeout - 1 - then - then")
      })
  })
  sleep(1000)
})

setTimeout(() => {
  console.log("setTimeout - 2")
  setTimeout(() => {
      console.log("setTimeout - 2 - 1")
      sleep(1000)
  })
  new Promise(resolve => resolve()).then(() => {
      console.log("setTimeout - 2 - then")
      new Promise(resolve => resolve()).then(() => {
          console.log("setTimeout - 2 - then - then")
      })
  })
  sleep(1000)
})

浏览器输出:

setTimeout - 1 //1为单个task
1s over
setTimeout - 1 - then
setTimeout - 1 - then - then 
setTimeout - 2 //2为单个task
1s over
setTimeout - 2 - then
setTimeout - 2 - then - then
setTimeout - 1 - 1
1s over
setTimeout - 2 - 1
1s over

node输出:

setTimeout - 1 
1s over
setTimeout - 2 //1、2为单阶段task
1s over
setTimeout - 1 - then
setTimeout - 2 - then
setTimeout - 1 - then - then
setTimeout - 2 - then - then
setTimeout - 1 - 1
1s over
setTimeout - 2 - 1
1s over

由此也可看出事件循环在浏览器和node中的不同。

参考文章

Tasks, microtasks, queues and schedules 强烈推荐

不要混淆nodejs和浏览器中的event loop 强烈推荐

node中的Event模块 强烈推荐

理解事件循环一(浅析)

Node 定时器详解

???

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93347.html

相关文章

  • 一篇文章教会你Event loop——览器Node

    摘要:如果没到毫秒,那么阶段就会跳过,进入阶段,先执行的回调函数。参考文档什么是浏览器的事件循环不要混淆和浏览器中的定时器详解浏览器和不同的事件循环深入理解事件循环机制篇中的执行机制 最近对Event loop比较感兴趣,所以了解了一下。但是发现整个Event loop尽管有很多篇文章,但是没有一篇可以看完就对它所有内容都了解的文章。大部分的文章都只阐述了浏览器或者Node二者之一,没有对比...

    Leck1e 评论0 收藏0
  • 览器Node事件循环(Event Loop)有何区别?

    摘要:事件触发线程主要负责将准备好的事件交给引擎线程执行。它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给引擎。 Fundebug经作者浪里行舟授权首发,未经同意请勿转载。 前言 本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的。 一、线程与进程 1. 概念 我们经常说 JS 是单线程执行的,...

    TANKING 评论0 收藏0
  • FE.ES-理解Event Loop

    摘要:新加了一个微任务和一个宏任务在当前执行栈的尾部下一次之前触发回调函数。阶段这个阶段主要执行一些系统操作带来的回调函数,如错误,如果尝试链接时出现错误,一些会把这个错误报告给。 JavaScript引擎又称为JavaScript解释器,是JavaScript解释为机器码的工具,分别运行在浏览器和Node中。而根据上下文的不同,Event loop也有不同的实现:其中Node使用了libu...

    longshengwang 评论0 收藏0
  • Event Loop - JS执行机制

    摘要:心塞塞根据规范,事件循环是通过任务队列的机制来进行协调的。等便是任务源,而进入任务队列的是他们指定的具体执行任务回调函数。然后当前本轮的结束,主线程可以继续取下一个执行。 依然是:经济基础决定上层建筑。 说明 首先,旨在搞清常用的同步异步执行机制 其次,暂时不讨论node.js的Event Loop执行机制,以下关于浏览器的Event Loop执行机制 最后,借鉴了很多前辈的研究文...

    muddyway 评论0 收藏0
  • JS与Node.js中事件循环

    摘要:的单线程,与它的用途有关。特点的显著特点异步机制事件驱动。队列的读取轮询线程,事件的消费者,的主角。它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给引擎。 这两天跟同事同事讨论遇到的一个问题,js中的event loop,引出了chrome与node中运行具有setTimeout和Promise的程序时候执行结果不一样的问题,从而引出了Nodejs的...

    abson 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<