彻底搞懂js和nodejs的事件循环eventloop

event loop 是一个执行模型,咱们这里可以简单的理解为就是js代码的执行模型。浏览器和NodeJS都实现了自己的event loop。

  • 浏览器的event loop 是在html5规范中明确定义的。
  • NodeJS的是基于libuv实现的,可以参考libuv的文档或者nodejs的文档。 libuv已经对event
  • loop做出了实现,而在html5规范中,只是定义了浏览器的event loop模型,具体实现交给了浏览器厂商。

**浏览器的eventloop执行模型:**宏任务队列和微任务队列
以下这些异步任务会进入宏任务队列

  • setTimeout
  • setInterval
  • setImmediate (Node独有)
  • requestAnimationFrame(浏览器独有)
  • I/O
  • UI rendering (浏览器独有)

以下异步任务会进入微任务队列micro task queue

  • process.nextTick (Node独有)
  • Promise(new promise 是同步代码)
  • Object.observe
  • MutationObserver

浏览器的Event Loop描述图,下图也是浏览器中js执行栈的描述
在这里插入图片描述
浏览器中event loop的执行过程:

  • stack 堆栈,所有的同步/异步代码都在这里,首先从上往下执行同步代码,遇到异步代码都会放到Background
    threads(后台程序)中。 当stack执行完毕后,stack会被全部清空。 这时候会依次执行为任务Microtask
  • Queue,直到微任务被执行完毕。如果微任务中包含微任务,会自动添加到队列末尾,这个周期内也会被执行。 这时候microtask
  • queue 和stack都是空,取出task queue位于队首的任务,执行完毕清空stack。继续执行步骤3。 重复3-4步骤

这里归纳3个重点:

  • 同步代码执行完成后,会先执行微任务队列。注意:此时会把所有微任务队列全部执行完。再去宏队列macrotask取出一个执行,如果宏任务执行过程中产生新的微任务,会跳过微任务继续执行并立即把微任务放到异步任务队列大池子中。当前条宏任务执行完后,又去检查待执行的任务队列大池子,如果有微任务,就优先执行微任务,如果没有就继续执行宏任务队列;
  • 微任务队列中所有的任务都会被依次取出来执行,直到microtask queue为空;
  • 图中没有画UI rendering的节点,因为这个是由浏览器自行判断决定的,但是只要执行UI rendering,它的节点是在执行完所有的microtask之后,下一个macrotask之前,紧跟着执行UI render。

看个例子:

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {
  console.log(6);
})

console.log(7);

结果:1475236

总结:

  • new promise()是一个常规macrotask宏任务,.then()是microtask微任务,代码会依次执行。
    在执行微队列microtask
  • queue中任务的时候,如果又产生了microtask,那么会继续添加到队列的末尾,也会在这个周期执行,直到microtask
    queue为空停止。
  • 注:当然如果你在microtask中不断的产生microtask,那么其他宏任务macrotask就无法执行了,但是这个操作也不是无限的,拿NodeJS中的微任务process.nextTick()来说,它的上限是1000个。

NodeJS的eventloop模型:
在这里插入图片描述
NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段,如下图:
在这里插入图片描述
各个阶段执行的任务如下:

  • timers阶段:这个阶段执行setTimeout和setInterval预定的callback I/O
  • callback阶段:执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks
  • idle, prepare阶段:仅node内部使用
  • poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里
  • check阶段:执行setImmediate()设定的callbacks close
  • callbacks阶段:执行socket.on(‘close’, …)这些callbacks

NodeJS中宏队列主要有4个

由上面的介绍可以看到,回调事件主要位于4个macrotask queue中:

  • Timers Queue
  • IO Callbacks Queue
  • Check Queue
  • Close Callbacks Queue

这4个都属于宏队列,但是在浏览器中,可以认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,但是在NodeJS中,不同的macrotask会被放置在不同的宏队列中。

NodeJS中微队列主要有2个:

  • Next Tick Queue:是放置process.nextTick(callback)的回调任务的 Other
  • Micro Queue:放置其他microtask,比如Promise等

在浏览器中,也可以认为只有一个微队列,所有的microtask都会被加到这一个微队列中,但是在NodeJS中,不同的microtask会被放置在不同的微队列中。

具体可以通过下图加深一下理解:
在这里插入图片描述
大体解释一下NodeJS的Event Loop过程:

  1. 执行全局Script的同步代码 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other
    Microtask Queue中的所有任务
  2. 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event
  3. Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2 Timers
  4. Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close
  5. Callback Queue -> 步骤2 -> Timers Queue …

这就是Node的Event Loop
在这里插入图片描述
setTimeout 和 setImmediate 的执行顺序是不确定的,当大家真正使用到的时候再深入了解吧。

setTimeout 对比 setImmediate

  • setTimeout(fn, 0)在Timers阶段执行,并且是在poll阶段进行判断是否达到指定的timer时间才会执行
  • setImmediate(fn)在Check阶段执行

两者的执行顺序要根据当前的执行环境才能确定:

  1. 如果两者都在主模块(main module)调用,那么执行先后取决于进程性能,顺序随机
  2. 如果两者都不在主模块调用,即在一个I/O Circle中调用,那么setImmediate的回调永远先执行,因为会先到Check阶段
    setImmediate 对比 process.nextTick
  3. setImmediate(fn)的回调任务会插入到宏队列Check Queue中
  4. process.nextTick(fn)的回调任务会插入到微队列Next Tick Queue中
  5. process.nextTick(fn)调用深度有限制,上限是1000,而setImmedaite则没有
    总结
  6. 浏览器的Event Loop和NodeJS的Event Loop是不同的,实现机制也不一样,不要混为一谈。
  7. 浏览器可以理解成只有1个宏任务队列和1个微任务队列,先执行全局Script代码,执行完同步代码调用栈清空后,从微任务队列中依次取出所有的任务放入调用栈执行,微任务队列清空后,从宏任务队列中只取位于队首的任务放入调用栈执行,注意这里和Node的区别,只取一个,然后继续执行微队列中的所有任务,再去宏队列取一个,以此构成事件循环。
  8. NodeJS可以理解成有4个宏任务队列和2个微任务队列,但是执行宏任务时有6个阶段。先执行全局Script代码,执行完同步代码调用栈清空后,先从微任务队列Next
  9. Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask
  10. Queue中依次取出所有的任务放入调用栈中执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行(注意,这里和浏览器不一样,浏览器只取一个),每个宏任务阶段执行完毕后,开始执行微任务,再开始执行下一阶段宏任务,以此构成事件循环。
  11. MacroTask包括: setTimeout、setInterval、
    setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering
  12. Microtask包括:
    process.nextTick(Node)、Promise、Object.observe、MutationObserver
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值