前端技术专家面试- Event Loop

57 阅读5分钟

核心内容:Event Loop设计理念;微任务的作用。

1. 背景

同步I/O、异步I/O

  1. 同步I/O
    • 进程向操作系统发起I/O;操作系统将进程挂起,开始执行I/O操作;等I/O操作完成,操作系统将数据放到进程可以访问的地方,重新激活进程;进程开始继续执行。
    • 特点:在进行I/O操作期间,进程是停止执行的,被I/O操作阻塞了。
  2. 异步I/O
    • 进程注册I/O操作回调,向操作系统发起I/O操作,然后进程继续;操作开始执行I/O操作;等I/O操作完成以后,操作系统在合适的时机让进程从回调函数处开始执行。
    • 特点:进程不会被I/O操作阻塞。

2. Event Loop的目的

JavaScript 主要是单线程的,这降低了并发编程的复杂度。

同时,JavaScript 需要处理各种潜在的耗时操作(如等待用户交互、发起网络请求等)。

为了保持高效性和响应性,JavaScript 采用了异步 I/O 模型。 为了协调 JavaScript 的执行和这些异步操作,需要一个特殊的组件,这就是 Event Loop。JavaScript 负责发起异步操作,而 Event Loop 负责管理这些操作的执行和结果的处理,在适当的时机通知 JavaScript 执行相应的回调函数或解析 Promise。

3. Event Loop的基本模型

  • 维护宏任务队列、微任务队列。
  • 队列接收来自各方提交的任务。
  • 循环执行任务队列中的回调函数。
    • 当空闲的时候,先执行微任务队列里面所有任务,然后执行一个宏任务,循环循环。

4. 微任务的作用

更细粒度的执行控制

5. 浏览器的Event Loop

  1. 宏任务:settimeout的回调,ajax的回调等
  2. 微任务:Promise的回调等、MutationObserver。
    • 更细粒度的执行控制:微任务允许在当前执行上下文结束后立即执行某些操作,而不需要等待整个宏任务队列。
    • 避免不必要的UI更新:在浏览器环境中,UI 更新通常发生在每个宏任务之后。使用微任务可以在 UI 更新之前完成相关操作,potentially 减少不必要的重绘。
    • 保证执行顺序:微任务可以确保某些相关的异步操作按预期顺序执行,而不被其他宏任务打断。
    • 提高响应速度:对于需要快速响应的操作(如 Promise 链),使用微任务可以确保它们在下一个渲染帧之前执行,提供更流畅的用户体验。
    • 优化资源使用:微任务可以更有效地利用每个宏任务周期,在进入下一个宏任务之前处理相关的小任务。
  3. 实现循环的执行流程:
    • 执行任务栈到结束;
    • 检测微任务队列,如果有则全部执行;
    • 检测宏任务队列,执行一个;
    • 如果需要,更新渲染
    • 循环这个过程

6. Node的Event Loop

  1. Node的Event Loop阶段更多,控制更加精细。

  2. 六个主要阶段:

    1. timers:执行 setTimeout 和 setInterval 的回调
    2. pending callbacks:执行延迟到下一个循环迭代的 I/O 回调
    3. idle, prepare:仅系统内部使用
    4. poll:检索新的 I/O 事件;执行 I/O 相关的回调
    5. check:执行 setImmediate() 的回调
    6. close callbacks:执行关闭的回调函数,如 socket.on('close', ...) Event Loop 会不断循环这些阶段,直到没有更多的异步操作需要处理。
  3. 微任务处理:

    • process.nextTick 的回调在每个阶段之间执行,优先级最高
    • Promise 的回调和其他微任务在每个阶段执行完后执行

7. libuv

  1. libuv 提供了 Node.js 事件循环的基础实现,而 Node.js 在此基础上构建了自己的事件循环机制。

  2. Node.js 的事件循环包含了 libuv 的核心功能,但同时也添加了特定于 Node.js 的抽象、阶段和功能。

  3. 核心功能:

    1. 跨平台支持
    2. 事件循环:libuv实现了一个事件驱动的编程模型,核心是其事件循环。
    3. 异步 I/O 操作:文件系统操作、网络操作(TCP, UDP)、DNS 操作。
    4. 线程池:libuv 维护了一个线程池,用于执行不能异步完成的阻塞操作(如某些文件系统操作)。
    5. 信号处理:提供了处理系统信号的机制。
    6. 定时器:实现了高精度的定时器功能。
  4. 工作流程

    • 任务提交:Node.js通过 libuv 的 API 提交异步操作请求。
    • 事件循环处理:libuv 的事件循环不断检查是否有完成的 I/O 操作、定时器到期等事件。
    • 回调执行:当操作完成时,libuv 将相应的回调函数加入到事件循环中,在适当的时机执行。
    • 继续循环:事件循环继续运行,处理新的事件和回调。
  5. 对非阻塞I/O的处理:对于可以非阻塞完成的操作(如网络 I/O),libuv 使用操作系统提供的非阻塞机制(如 epoll、kqueue、IOCP)。

  6. 线程池处理:对于无法异步完成的操作(如某些文件系统操作),libuv 将任务提交到其内部的线程池。