事件循环 Event Loop

作者:Clloz
出处:
前言本文分析 JavaScript 的事件循环 Event Loop 的执行机制和细节 。Event Loop 标准文档见 Event Loop – whatwg
Event LoopEvent Loop 是 JS 宿主环境的一个设施(浏览器和 nodejs 都实现了) , 它不是 JS 引擎的一部分 。JS 引擎负责代码的执行 , 主要包括了一个内存堆和一个调用栈 。 而 Event Loop 的主要作用就是对异步任务何时进入引擎执行进行管理 。
先了解三种数据结构

  1. 栈( stack ):栈在计算机科学中是限定仅在表尾进行插入或删除操作的线性表 。栈是一种数据结构 , 它按照后进先出的原则存储数据 , 先进入的数据被压入栈底 , 最后的数据在栈顶 , 需要读数据的时候从栈顶开始弹出数据 。 栈是只能在某一端插入和删除的特殊线性表 。
  2. 堆( heap ):堆是一种数据结构 , 是利用完全二叉树维护的一组数据 , 堆分为两种 , 一种为最大堆 , 一种为最小堆 , 将根节点最大的堆叫做最大堆或大根堆 , 根节点最小的堆叫做最小堆或小根堆 。 堆是线性数据结构 , 相当于一维数组 , 有唯一后继 。
  3. 队列( queue ):特殊之处在于它只允许在表的前端( front )进行删除操作 , 而在表的后端( rear )进行插入操作 , 和栈一样 , 队列是一种操作受限制的线性表 。 进行插入操作的端称为队尾 , 进行删除操作的端称为队头 。队列中没有元素时 , 称为空队列 。 队列的数据元素又称为队列元素 。 在队列中插入一个队列元素称为入队 , 从队列中删除一个队列元素称为出队 。 因为队列只允许在一端插入 , 在另一端删除 , 所以只有最早进入队列的元素才能最先从队列中删除 , 故队列又称为先进先出( FIFO—first in first out ) 。
javaScript 是单线程 , 也就是说只有一个主线程 , 主线程有一个栈 , 每一个函数执行的时候 , 都会生成新的 execution context (执行上下文) , 执行上下文会包含一些当前函数的参数、局部变量之类的信息 , 它会被推入栈中 ,running execution context (正在执行的上下文)始终处于栈的顶部 。 当函数执行完后 , 它的执行上下文会从栈弹出 。 把 JS 执行设施再细分有三个部分( 1 和 2 为 JS 引擎中的):
  1. Stack :主线程的函数执行都压在这个栈中 。
  2. Heap :存放对象 , 数据 。 没有引用的对象会被垃圾回收 。
  3. Task Queue :执行栈为空的时候从任务队列中取一个任务执行 , 再次为空时再次到任务队列中取任务执行 , 如此循环 , 所以称为 Event Loop。

事件循环 Event Loop文章插图
具体过程如下图:
事件循环 Event Loop文章插图
Javascript 引擎是单线程 , 任务被分为同步任务和异步任务 , 同步任务会在调用栈中按照顺序等待主线程依次执行 , 异步任务会先进入 Event Table 并注册 , 在异步任务完成后 ,Event Table 会将注册的回调函数放入事件队列中等待主线程空闲的时候(调用栈被清空) , 被读取到栈内等待主线程的执行 。 图中的异步处理模块就是我们之前提到的事件触发线程 , 当 JS 引擎遇到异步任务的时候就把异步函数交给事件触发线程 , 当异步函数达到执行条件之后 , 事件触发线程会把异步任务根据类型压入指定任务队列 。
任务队列
JS 中 , 有两类任务队列:宏任务队列( macro tasks )和微任务队列( micro tasks ) 。 宏任务队列可以有多个 , 微任务队列只有一个 。
  • 宏任务: script (全局任务), setTimeout , setInterval , setImmediate , I/O , UI rendering .
  • 微任务: process.nextTick , Promise , MutationObserver .
ajax 请求不属于宏任务 ,js 线程遇到 ajax 请求 , 会将请求交给对应的 http 线程处理 , 一旦请求返回结果 , 就会将对应的回调放入宏任务队列 , 等请求完成执行 。
浏览器环境下 ,Event Loop 是按照 HTML5 的标准来实现 , 当执行栈空的时候 JS 引擎会按如下规则取任务执行:
1. 取一个宏任务来执行 。 执行完毕或没有宏任务 , 下一步 。
2. 取一个微任务来执行 , 执行完毕后 , 再取一个微任务来执行 。 直到微任务队列为空 , 执行下一步 。
3. 更新UI渲染 。
Event Loop 会无限循环执行上面3步 , 这就是 Event Loop 的主要控制逻辑 。 其中 , 第 3 步(更新 UI 渲染)会根据浏览器的逻辑 , 决定要不要马上执行更新 , 一般会在下一次宏任务执行前进行渲染 。 毕竟更新 UI 成本大 , 所以 , 一般都会比较长的时间间隔 , 执行一次更新 。 这个 可视化 JS 执行过程 可以帮助你理解 。