浏览器事件循环js事件循环node事件循环基础理解

文章目录

    • 1.浏览器的事件循环
      • MacroTask
      • MicroTask
      • 执行顺序
    • 2.JavaScript中的的事件循环
      • MacroTask
      • MicroTask
      • 代码执行顺序
    • 3.NodeJs的事件循环
      • 执行过程
    • 参考资料

浏览器事件循环js事件循环node事件循环基础理解_第1张图片

JavaScript语言的一大特点就是单线程,为了提升cpu的利用率(很多时候CPU是闲着的,因为IO操作很慢,如Ajax操作从网络读取数据,就不得不等着结果出来,再往下执行)因此任务可以区分为两种:

任务类型 解释
同步任务(synchronous) 在主线程(执行栈)上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务(asynchronous) 异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

同步异步、阻塞非阻塞参考另一篇文章:同步异步、阻塞非阻塞

异步任务会被丢到任务队列,在主线程空了的时候才去任务队列里取事件,执行实际的逻辑,如图:
浏览器事件循环js事件循环node事件循环基础理解_第2张图片

浏览器,js, nodejs都有事件循环,具体的方式有一定的差别

1.浏览器的事件循环

事件循环只是一个宏观的表述,为了区分异步任务之间的优先级,异步任务又可以区分为两类

  • Macro Task
  • Micro Task

MacroTask

常见的MacroTask有下面几种(注意都是异步的)

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

MicroTask

常见的MicroTask如下面几种(注意都是异步的)

  • Promise
  • Object.observe
  • MutationObserver

联想:MicroTask可能会影响页面渲染?=> 是,比如循环调用一个microTask就会阻塞页面渲染

执行顺序

当主线程中JavaScript代码全部执行完毕,这时Js解释器会从任务队列里面取异步任务进行执行。然而,取任务也根据任务的优先级(Macro还是Micro)有一定区分:
1.宏队列macrotask一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
2.微任务队列中所有的任务都会被依次取出来执行,直到microtask queue为空;
3.UI rendering 是由浏览器自行判断决定的,但是只要执行UI rendering,它的节点是在执行完所有的microtask之后,下一个macrotask之前,紧跟着执行UI render。

  1. 解释器会先将所有MicroTask取出执行,执行结束才会取出一个MacroTask执行,以此循环
  2. 一般认为浏览器里有一个 MicroTask 和一个 MacroTask, 但是实际上浏览器内部为了更快的响应用户UI,内部可能是有多个task queue的,UI的MacroTask queue的优先级可能比较高
  3. 怎样算一轮 event loop?
    我的理解, 不知道对否:
    (1)如果是有 I/O 操作,的,则是由 I/O 起头的,先是 MacroTask执行一个(因为 I/O 操作是属于 MacroTask), 再执行 MicroTask 的所有任务, 然后是 UI-render
    (2)如果是执行 js 代码,先按顺序执行同步的,再执行所有的 MicroTask, 然后再执行 MacTask 中的一个,如果执行完一个又有 MicroTask, 则开始新的一轮 event loop,去执行 MicroTask
    如果是 nodejs 里执行,见下方 node 的描述
  4. 执行顺序
    一个掘金的老哥(ssssyoki)的文章摘要:
    js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue(但是没有执行宏任务的回调),然后在执行微任务,将微任务放入eventqueue最骚的是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回掉函数,然后再从宏任务的queue上拿宏任务的回掉函数。 我当时看到这我就服了还有这种骚操作。

2.JavaScript中的的事件循环

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。比如IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。(由此可见,异步队列里放的是异步操作的回调函数,而不是操作本身,放到异步队列就说明该操作已经完成了,就等着调用回调函数了)

MacroTask

  • setTimeout,
  • setInterval,
  • 用户产生的事件(比如鼠标点击、页面滚动等等)

但是有个例外:

  1. "定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。定时器是到了规定时间才将函数放到队列里,不是一开始就放进队列里等待到时间再执行,因为一旦进入了队列就没法保证它的执行时间。
    "任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,由于存在"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
  2. setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。(即排在异步任务队列的最后一个,它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行)

联想setTimeout为0,实际执行的时间间隔最少能到多少? => 4ms

MicroTask

  • 原生Promise(有些实现的promise将then方法放到了宏任务中)

  • Object.observe(已废弃) ,废弃原因参考Object.observe为何要被移除?
    浏览器事件循环js事件循环node事件循环基础理解_第3张图片

  • MutationObserver 记住就行了

代码执行顺序

模拟js引擎执行顺序 (比较重要)
可以在开始前先将异步的内容注释为一个整体,比如:a, 注意 process.nextTick() 里的函数是回调函数,不会立刻进入调用栈,而是会进入 microTask 队列,而 new Promise() 里 .then 之前的内容都是立即进入调用栈的,属于同步内容,只有 .then 里的内容才是异步回调函数,进入 microTask 队列;setTimeout 同 process.nextTick()。尤其是倒数第二个例子,一定要看清楚是在 .then 之前还是之后

3.NodeJs的事件循环

NodeJS中Macro Task队列主要有4个

  • Timers Queue:执行setTimeout和setInterval预定的callback
  • IO Callbacks Queue:执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()(Node独有)设定的callbacks这些之外的callbacks
  • Check Queue:执行setImmediate()设定的callbacks
  • Close Callbacks Queue:执行socket.on(‘close’, …)这些callbacks

在浏览器中,可以认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,但是在NodeJS中,不同的macrotask会被放置在不同的宏队列中。
?js 的异步队列有几个呢?还是说 js 的事件循环就是浏览器的事件循环?

NodeJS中Micro Task队列主要有2个

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

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

另一种理解方式:process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在下一次"任务队列"的尾首添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像,它指定的任务总是在下一次Event Loop时执行,setImmediate总是将事件注册到下一轮Event Loop,总的来说:nextTick 是放到当前队列的队尾,setImmediate 是在下一次队列的队首,setTimeout在二者中间

执行过程

  • 执行全局Script的同步代码
  • 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
  • 执行macrotask宏任务,宏任务共6个阶段,从第1个阶段开始执行该阶段宏任务队列的所有任务(和浏览器的事件循环有区别,在浏览器的事件循环中是只取宏队列的第一个任务出来执行),每一个阶段的宏任务执行完毕后,执行微任务(也就是步骤2),然后再进行第2个阶段的宏任务队列,依次循环

总结如下:
浏览器事件循环js事件循环node事件循环基础理解_第4张图片

  • Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue …

  • 拓展
    Node 全局变量 上有个属性: setImmediate
    类似于 setTimeout, setInterval,但它没有时间,执行时间是在下一次事件循环,
    Process 上有个nextTick , 对比于全局的 setImmediate: 前者更早, stTimeout 第二,最后是setImmediate
    Process.nextTick 是当前队列的最后一个,setImmediate 是下次循环的队首。不知道用哪个的情况下,选 setImmediate, 因为 Process.nextTick 里如果再调用一个 Process.nextTick 的话,就会造成循环,
    setTimeOut:指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行,所以 setTimeOut 在 process.nextTick 之后

  • 有个问题:promise 和 process.nextTick 相比,哪个在前哪个在后?
    node 里宏任务:
    代码script,setTimeout,setInterval、setImmediate
    node 里微任务:
    promise, process.nextTick

参考资料

  • 带你彻底弄懂Event Loop
  • JavaScript 运行机制详解:再谈Event Loop
  • JS事件循环机制(event loop)之宏任务/微任务
  • JavaScript事件循环机制
  • 详解JavaScript中的Event Loop(事件循环)机制
  • 执行顺序

你可能感兴趣的:(javascript机制,web前端,前端,javascript,node.js,chrome)