添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

1. 什么是eventLoop?

它是一个在 JavaScript 引擎等待任务,执行任务和进入休眠状态等待更多任务这几个状态之间转换的无限循环。 我们都知道JavaScript引擎是单线程的,至于为什么是单线程主要是出于JavaScript的使用场景考虑,作为浏览器的脚本语言,js的主要任务是主要是实现用户与浏览器的交互,以及操作dom,如果设计成多线程会增加复杂的同步问题。想象一个场景:多个线程同时操作dom,浏览器渲染引擎该使用哪个线程的结果。当然为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

虽然JS是单线程的,但浏览器却是多线程,其中几个典型的线程已经在图中表示出来,而 eventLoop就是沟通JS引擎线程和浏览器线程的桥梁,也是浏览器实现异步非阻塞模型的关键。

2. 宏队列和微队列

宏队列,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:

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

微队列,microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:

  1. process.nextTick (Node独有)
  2. Promise
  3. Object.observe
  4. MutationObserver

3. 浏览器事件循环流程

3.1 浏览器EventLoop的具体流程:

  1. js引擎将所有代码放入执行栈,并依次弹出并执行,这些任务有的是同步有的是异步(宏任务或微任务)。
  2. 如果在执行 栈中代码时发现宏任务则交个浏览器相应的线程去处理,浏览器线程在正确的时机(比如定时器最短延迟时间)将宏任务的消息(或称之为回调函数)推入宏任务队列。而宏任务队列中的任务只有执行栈为空时才会执行。
  3. 如果执行 栈中的代码时发现微任务则推入微任务队列,和宏任务队列一样,微任务队列的任务也在执行栈为空时才会执行,但是微任务始终比宏任务先执行。
  4. 当执行栈为空时,eventLoop转到微任务队列处,依次弹出首个任务放入执行栈并执行,如果在执行的过程中又有微任务产生则推入队列末尾,这样循环直到微任务队列为空。
  5. 当执行栈和微任务队列都为空时,eventLoop转到宏任务队列,并取出队首的任务放入执行栈执行。需要注意的是宏任务每次循环只执行一个。
  6. 重复1-5过程
  7. …直到栈和队列都为空时,代码执行结束。引擎休眠等待直至下次任务出现。

3.2 在这个过程中有三个重点:

  1. 宏任务每次只取一个,执行之后马上执行微任务。
  2. 微任务会依次执行,直到微任务队列为空。
  3. 图中没有画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);
  Promise.resolve().then(() => {
    console.log(6)
  }).then(() => {
    console.log(7)
    setTimeout(() => {
      console.log(8)
    }, 0);
  });
setTimeout(() => {
   console.log(9)
}, 0);
console.log(10)

答案:1,4,10,5,6,7,2,3,9,8 你做对了吗? 如果还不明白的话对照图在走一次。

4. Node.JS事件循环流程

在这里插入图片描述
可以看出Node.JS的事件循环比浏览器端复杂很多。

1. NodeJS中的宏队列和微队列

1.1 事实上NodeJS中执行宏队列的回调任务有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。
1.2 其中宏队列有4个,各种类型的任务主要集中在以下四个队列之中:
  • Timers Queue
  • IO Callbacks Queue
  • Check Queue
  • Close Callbacks Queue
微队列主要有2个,不同的微任务放在不同的微队列中:
  • Next Tick Queue:是放置process.nextTick(callback)的回调任务的
  • Other Micro Queue:放置其他microtask,比如Promise等
6. Node的 EventLoop的具体流程:
  1. 执行全局Script的同步代码。
  2. 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务。
  3. 执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2。
  4. Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue …
  5. 重复1 - 4过程。

下面做一个小测试吧

console.log(0);
setTimeout(() => {          // callback1
  console.log(1);
  setTimeout(() => {        // callback2
    console.log(2);
  }, 0);
  setImmediate(() => {      // callback3
    console.log(3);
  process.nextTick(() => {  // callback4
    console.log(4);  
}, 0);
setImmediate(() => {        // callback5
  console.log(5);
  process.nextTick(() => {  // callback6
    console.log(6);  
setTimeout(() => {          // callback7              
  console.log(7);
  process.nextTick(() => {  // callback8
    console.log(8);   
}, 0);
process.nextTick(() => {    // callback9
  console.log(9);  
console.log(10);

答案:0, 10, 9, 1, 4, 7, 8, 5, 6, 3, 2 。

7. 总结

  1. 事件循环是 浏览器 和 Node 执行JS代码的核心机制,但浏览器 和 NodeJS事件循环的实现机制有些不同。
  2. 浏览器事件循环有一个宏队列,一个微队列,且微队列在执行过程中一个接一个执行一直到队列为空,宏队列只取队首的一个任务放入执行栈执行,执行过后接着执行微队列,并构成循环。
  3. NodeJS事件循环有四个宏队列,两个微队列,微队列执行方式和浏览器的类似,先执行Next Tick Queue所有任务,再执行Other Microtask Queue所有任务。 但宏队列执行时会依次执行队列中的每个任务直至队为空才开始再次执行微队列任务。
  4. MacroTask包括: setTimeout、setInterval、 setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering
  5. Microtask包括: process.nextTick(Node)、Promise、Object.observe、MutationObserver

8. 后记

学习事件循环会让我们对JS引擎执行代码流程有一个大概的了解,如果遇到任务执行顺序带来的问题,我们也能更快的解决。 同时也会让我们对异步编程有一个更深的认识。

1. 什么是eventLoop?它是一个在 JavaScript 引擎等待任务,执行任务和进入休眠状态等待更多任务这几个状态之间转换的无限循环。 我们都知道JavaScript引擎是单线程的,至于为什么是单线程主要是出于JavaScript的使用场景考虑,作为浏览器的脚本语言,js的主要任务是主要是实现用户与浏览器的交互,以及操作dom,如果设计成多线程会增加复杂的同步问题。想象一个场景:多个线程同时操作dom,浏览器渲染引擎该使用哪个线程的结果。当然为了利用多核CPU的计算能力,HTML5提出Web W
前端面试系列-JavaScriptEvent Loop(事件循环)机制javascript是一门单线程的非阻塞的脚本语言。 浏览器环境下js引擎的事件循环机制 执行栈与事件队列 执行上下文 事件队列(Task Queue) 事件循环Event Loop) 微任务(micro task)和宏任务(macro task) node环境下的事件循环机制 1.与浏览器环境的差异 2.事件循环模型 3.事件循环各阶段详解
理解事件循环之前先要知道何为异步同步,(事件循环看最后一句)   同步:程序按顺序连续执行     在主线程上排队执行任务。前一个完毕,后一个才执行 如果要等,会一直等待下去,直到收到消息再执行​。 阻塞​   异步:程序不连续分段执行     为了不阻塞、不浪费等待的时间   比喻:事情可以分为前半段和后半段,同步是前半段后半段同时按顺序一起完成。异步是先做前半段,后半段有时间再做。 ...
什么是事件循环机制? 事件循环分为两种,分别是浏览器事件循环node.js事件循环,本文主要对浏览器事件循环进行描述。 我们都知道JavaScript是一门单线程语言,指主线程只有一个。Event Loop事件循环,其实就是JS引擎管理事件执行的一个流程,具体由运行环境确定。目前JS的主要运行环境有两个,浏览器Node.js。 先了解浏览器的进程和线程: 浏览器是多进程的,浏览器每一个打开一个Tab页面都代表着创建一个独立的进程。渲染进程(浏览器内核)是多线程的,也是浏览器的重点,因为页面的渲染,JS执
Fundebug经作者浪里行舟授权首发,未经同意请勿转载。 本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node Event Loop 其实是不相同的。 一、线程与进程 1. 概念 我们经常说 JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程? 官方的说法是:进程是 CPU 资...
Node.js启动时会初始化event loop, 每一个event loop都会包含按如下六个循环阶段,nodejs事件循环浏览器事件循环完全不一样。 注意: 图的每个方框被称作事件循环的一个”阶段(phase)”, 这6个阶段为一轮事件循环。 timers(定时器) : 此阶段执行那些由 setTimeout() 和 setInterval() 调度的回调函数. NodeEvent Loop 什么是Node.js Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。 而I/O处理方面使用了自己设计的libuv,libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作...
node和浏览器都是js运行的环境,二者都给js提供了一个很强大的功能,事件循环。那么什么是事件循环呢,简单讲就是在单线程为了实现程序的高效运行而设定的事件执行机制