EventLoop

1. EventLoop的执行流程图

  ┌───────────────────────┐
┌─>│ timers │<————— 执行 setTimeout()、setInterval() 的回调
│ └──────────┬────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│ ┌──────────┴────────────┐
│ │ pending callbacks │<————— 执行由上一个 Tick 延迟下来的 I/O 回调(待完善,可忽略)
│ └──────────┬────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│ ┌──────────┴────────────┐
│ │ idle, prepare │<————— 内部调用(可忽略)
│ └──────────┬────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
| | ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │ - (执行几乎所有的回调,除了 close callbacks 以及 timers 调度的回调和 setImmediate() 调度的回调,在恰当的时机将会阻塞在此阶段)
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ | | |
| | └───────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
| ┌──────────┴────────────┐
│ │ check │<————— setImmediate() 的回调将会在这个阶段执行
│ └──────────┬────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│ ┌──────────┴────────────┐
└──┤ close callbacks │<————— socket.on('close', ...)
  1. setTimeout/setInterval 属于 timers 类型;
  2. setImmediate 属于 check 类型;
  3. socket 的 close 事件属于 close callbacks 类型;
  4. 其他 MacroTask 都属于 poll 类型。
  5. process.nextTick 本质上属于 MicroTask,但是它先于所有其他 MicroTask 执行;
  6. 所有 MicroTask 的执行时机,是不同类型 MacroTask 切换的时候。
  7. idle/prepare 仅供内部调用,我们可以忽略。
  8. pending callbacks 不太常见,我们也可以忽略。

2. 执行机制

  1. 先执行所有类型为 timers 的 MacroTask,然后执行所有的 MicroTask(注意 NextTick 要优先哦);
  2. 进入 poll 阶段,执行几乎所有 MacroTask,然后执行所有的 MicroTask;
  3. 再执行所有类型为 check 的 MacroTask,然后执行所有的 MicroTask;
  4. 再执行所有类型为 close callbacks 的 MacroTask,然后执行所有的 MicroTask;
  5. 至此,完成一个 Tick,回到 timers 阶段;

    ……

    如此反复,无穷无尽……

2.1 实例理解

const macroTaskList = [
['task1'],
['task2', 'task3'],
['task4'],
] for (let macroIndex = 0; macroIndex < macroTaskList.length; macroIndex++) {
const microTaskList = macroTaskList[macroIndex] for (let microIndex = 0; microIndex < microTaskList.length; microIndex++) {
const microTask = microTaskList[microIndex] // 添加一个微任务
if (microIndex === 1) microTaskList.push('special micro task') // 执行任务
console.log(microTask)
} // 添加一个宏任务
if (macroIndex === 2) macroTaskList.push(['special macro task'])
} // 输出结果:
// > task1
// > task2
// > task3
// > special micro task
// > task4
// > special macro task

2.2 执行细节分析

2.2.1 试分析下面程序的执行结果

console.log(1)
setTimeout(function() {
console.log(2)
}) Promise.resolve()
.then(function() {
console.log(3)
}) console.log(4)

2.2.2 执行流程分析

stack(执行栈)、Micro(微任务)、Macro(宏任务)

  1. 初始状态: stack:[], Micro: [], Macro: [script]。执行栈为空, 微任务为空, 宏任务队列中有一个整体的 script代码

  2. 主线程开始执行, 遇到console.log(1), 首先会打印 1

  3. 继续向下执行,遇到 setTimeout异步任务,就将其加入到Macro(宏任务)队列中。等待执行

  4. 继续向下执行, 遇到 Promise.resolve也是一个异步任务,单它是微任务,将其加入 Micro(微任务)队列中,等待着行

  5. 解析console.log(4), 并且打印4。 当主线程执行完打印的结果依次是 1 和 4。

  6. 这时候主线程就会问 任务(异步)队列,有没有微任务要执行,将所有的 Micro(微任务)加入执行栈执行, 打印结果 3

  7. 微任务执行完了, 就开始下一轮事件循环, 将第一个 Macro(宏任务)压入执行栈执行, 再次打印 2。

3. 谈一下宏任务与微任务的区别?(面试重点)

[!NOTE]

面试常考点,关键在于理解EventLoop的机制,以及宏任务和微任务的底层原理。

3.1 宏任务

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame

常见的宏任务: setTimeout、setInterval、setImmediate、 script中整体的代码、 I/O操作、 UI渲染等。

3.2 微任务

  • process.nextTick
  • MutationObserver
  • Promise.then catch finally

常见的微任务有: process.nextTick、Promise和 MutationObserver监听DOM的变化。

3.3 微任务和宏任务的区别

[!NOTE]

  • 微任务进入主线程执行是一队一队的, 而宏任务进入主线程是一个一个的。
  • 微任务是在主线程空闲时批量执行, 宏任务是在事件循环下一轮的最开始执行

参考文章:

【前端知识体系-NodeJS相关】对于EventLoop(事件轮询)机制你到底了解多少?的更多相关文章

  1. 前端知识体系-NodeJS相关】NodeJS基础知识全面总结

    NodeJS基础知识 1. Node的全局对象和全局变量 1.1 全局对象:所有模块都可以调用的 global:表示Node所在的全局环境,类似于浏览器的window对象. process:该对象表示 ...

  2. 【前端知识体系-NodeJS相关】NodeJS高频前端面试题整理

    1. 为什么JavaScript是单线程? 防止DOM渲染冲突的问题: Html5中的Web Worker可以实现多线程 2.什么是任务队列? 任务队列"是一个先进先出的数据结构,排在前面的 ...

  3. 【前端知识体系-NodeJS相关】对NodeJS模块机制的理解

    1. CommonJS模块规范 1.1 模块引用 var math = require('math'); 1.2 模块定义 [!NOTE] 上下文提供exports对象用于导出当前模块的方法和变量,并 ...

  4. 【前端知识体系-NodeJS相关】浅谈NodeJS中间件

    1. 中间件到底是个什么东西呢? [!NOTE] 中间件其是一个函数,在响应发送之前对请求进行一些操作 function middleware(req,res,next){ // 做该干的事 // 做 ...

  5. 12.nodejs事件轮询机制

    一:nodejs事件轮询机制  就是  函数的执行顺序 <script type="text/javascript"> setImmediate(function(){ ...

  6. 面试题: nodejs 的事件轮询机制

    setTimeout(function(){ console.log('setTimeout()执行了') },0) setImmediate(function(){ console.log('set ...

  7. JS中的异步以及事件轮询机制

    一.JS为何是单线程的? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊.(在JAVA和c#中的异步 ...

  8. nodejs的事件轮询机制

    1.timers定时器阶段 执行定时器到点的回调函数(所有定时器setTimeout / setInterval的回调函数都在这个阶段执行) 2.idle prepare 准备阶段 TCP错误回调 3 ...

  9. 【前端知识体系-JS相关】JS基础知识总结

    1 变量类型和计算 1.1 值类型和引用类型的区别? 值类型:每个变量都会存储各自的值.不会相互影响 引用类型:不同变量的指针执行了同一个对象(数组,对象,函数) 1.2 typeof可以及检测的数据 ...

随机推荐

  1. [洛谷P1373][题解]小a和uim之大逃离

    (别点我我不是题目) 这道题可以很容易看出是一道dp(因为是在dp关卡里找的) 稍微想一下就可以yy出一个不错的状态: f[i][j][k][0/1]代表走到了点(i,j).膜液量相差k(小a-uim ...

  2. git版本对比

    1.git diff版本比对 (未添加到暂存区间之前的区别对比) 未进行修改,则显示为空 进行文件修改,再执行git diff 当执行git add .  之后,再次git diff则为空 缓存和提交 ...

  3. MyBatis的ResultMapping和ResultMap

    MyBatis的ResultMapping和ResultMap Effective java 第3版中描述的Builder模式 Java设计模式14:建造者模式 2个类都使用了Builder来构建对象 ...

  4. 深入浅出14个Java并发容器

    前言 不考虑多线程并发的情况下,容器类一般使用ArrayList.HashMap等线程不安全的类,效率更高.在并发场景下,常会用到ConcurrentHashMap.ArrayBlockingQueu ...

  5. 利用Python进行数据分析-Pandas(第二部分)

    上一个章节中我们主要是介绍了pandas两种数据类型的具体属性,这个章节主要介绍操作Series和DataFrame中的数据的基本手段. 一.基本功能 1.重新索引 pandas对象的一个重要方法是r ...

  6. eclipse的一些常用快捷键

    掌握了eclipse快捷键功能,能够大大提高开发效率. 这里总结一些eclipse的常用快捷键. 编辑相关快捷键  1. [ALT+/]:此快捷键为用户编辑的好帮手,能为用户提供内容的辅助,不要为记不 ...

  7. C#基本语法<一>_入门经典

    基本信息 CIL和JIT CIL通用中间语言 JIT just-in-time使得CIT代码仅在需要时才编译 程序集 包含可执行文件.exe和库函数.dll和资源文件,不必把程序集集中到一个地方,全局 ...

  8. 松软科技web课堂:SQLServer之ROUND() 函数

    ROUND() 函数 ROUND 函数用于把数值字段舍入为指定的小数位数. SQL ROUND() 语法 SELECT ROUND(column_name,decimals) FROM table_n ...

  9. 设计好的报表是如何在 web 上显示的

    润乾报表主要用来开发在 java web 端呈现的报表工具,设计器为桌面版,那么开发后的报表如何发布到 web 端是用户较为关心的问题. 润乾报表 web 端发布,采用 Taglib 方式,只需要在 ...

  10. Pandas处理超大规模数据

    对于超大规模的csv文件,我们无法一下将其读入内存当中,只能分块一部分一部分的进行读取: 首先进行如下操作: import pandas as pd reader = pd.read_csv('dat ...