聊聊事件循环机制

本篇主要讨论的是JavaScipt的事件循环机制(event loop),主要分为以下几个部分:

  1. 为什么需要事件循环机制
  2. 事件循环机制的表现
  3. 有哪些使用场景
  4. nodejs的事件循环(待补充)

为什么需要事件循环机制

众所周知,在浏览器中,JavaScript 是单线程的(原因在此处不做过多赘述),也就是说所有的事件都在一个线程中执行,这个线程负责处理:

  • 用户交互事件
  • DOM元素更新、绘制
  • 执行js代码

我们知道,当我们调用一个方法的时候,js 会生成一个与这个方法对应的执行环境(context),又叫执行上下文。
这个执行环境中存在着这个方法的私有作用域,上层作用域的指向,方法的参数,这个作用域中定义的变量以及这个作用域的this对象。 当一系列这样的方法被依次调用的时候,js 会将他们放入 执行栈 中依次执行(排队)。

如果某个任务执行时间过长,后面的任务则会一直处于等待状态,任务执行效率低且用户体验也不好,所以有了异步任务 & 事件循环机制

在 js 中,常用的异步编程方式有回调函数Promiseasync/await 等。这些方式都可以让 js 代码异步执行,从而避免长时间阻塞 UI 线程。而异步任务的关键就在于任务队列,当异步任务开始执行的,会放入这个任务队列,执行完成之后,再从任务队列弹出,这个下面详细说明。

事件循环机制的表现

js中任务主要分为:

  • 同步任务:马上可以拿到结果,耗时较短
  • 异步任务:不知道什么时候可以拿到结果,时间未知

执行栈 中出现了异步任务是一件很痛苦的事,所以我们可以不看让异步任务另外排队呢,这样也不影响同步任务的执行效率,是的,于是js中有了事件队列(Task Queue)的概念:
js引擎遇到一个异步任务后并不会一直等待其返回结果,而是会将这个任务挂起,继续执行执行栈中的其他任务。当一个异步任务返回结果后,js会将这个任务加入另外一个队列,我们称之为事件队列,事件队列中的任务不会马上执行,需要等到执行栈中的同步任务全部执行完成之后,主线程轮训查看事件队列中是否有任务需要执行,是的轮训,因为代码执行过程中会有新的 同步任务/异步任务 排进执行栈,所以主线程会一直在执行栈任务队列中轮训,其实这大概就是事件循环的机制了,整体流程大概如下图所示。

如果在异步任务完成或放入执行栈执行,有新的同步任务产生,则会导致异步任务延迟执行。比如可能会导致 setTimeout 不准

event loop

但是,还没完。。。
异步任务分为很多种且执行优先级不一样,所以对于异步任务又做了细分:宏任务、微任务

宏任务包括有

  • Http请求、
  • setTimeout
  • setInterval
  • requestAnimationFrame

微任务包括有:

  • promise.then
  • promise.catch
  • promise.finally
  • MutationObserver

微任务一定优先于宏任务
微任务是线程之间的切换,速度快,不需要进行上下文切换
宏任务是进程之间的切换,速度慢,每次执行需要切换上下文

上面提到过,异步任务返回结果后会被放入事件队列中。然而,为了细分宏任务微任务的执行顺序,会根据任务类型将任务放入对应的宏任务队列或微任务队列。
当在执行栈为空的时候,主线程会查看微任务队列是否有事件存在。

  • 不存在,会在宏任务队列中取出一个任务放入执行栈
  • 存在,执行微任务队列中的任务,直至为空,然后在宏任务队列中取出一个任务放入执行栈
    等待执行栈的任务完成/清空后,再依次检查 微任务队列宏任务队列等等
    以上就是JavaScript的事件循环机制
COPY
1
2
3
4
// setTimeout设置为0时也属于宏任务
setTimeout(() => {
... ...
}, 0);

注意:在每个循环的末尾,会进行一次dom的更新渲染,vue中$nextTick与此有异曲同工之处,或者说是借鉴了这里

有哪些使用场景

  • 使用 setTimeout对不重要的事件,同步任务异步化,避免主线程阻塞
  • $nextTick的实现:$nextTick内部实现优先级:微任务setImmediate、微任务MessageChannel、宏任务setTimeout、Promise.then
  • 待补充

nodejs事件循环

待补充

作者: 果汁
文章链接: https://guozhigq.github.io/post/8f637f35.html
版权声明: All posts on this blog are licensed under the CC BY-NC-SA 4.0 license unless otherwise stated. Please cite 果汁来一杯 !