Event Loop 事件循环模型

684 阅读3分钟

Event Loop

Event Loop 翻译过来叫 事件循环. 分为:

  1. 浏览器 Event Loop
  2. node Event Loop

浏览器 Event Loop

在讲浏览器 Event Loop之前,我们需要了解宏任务与微任务 以及 执行栈stack; 堆heap;队列callback queue的含义。

执行栈 stack:

execution stack 所有的代码都是在此空间中执行的。

堆 heap

对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语。

任务队列callback queue

所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout定时函数等都属于异步任务,异步任务会通过任务队列(Event Queue)的机制来进行协调。

一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。

其中:任务队列放着宏任务1,宏任务2...宏任务n;微任务1,微任务2...微任务n;

js是单线程执行的,是因为保证只能由一个线程去操作DOM。

Event Loop 流程

一个Event Loop有一个或多个宏任务队列, 有一个微任务队列。 image.png 通过上图我们可以看出,事件循环的过程:

  1. 执行初始化(script全局执行)代码, 将异步事件回调函数交给对应模块(线程)管理。
  2. 当事件被触发时, 管理模块(线程)会将回调函数及其数据添加到对应的回调列队中。
  3. 只有当初始化代码(script全局执行代码,也被称为宏任务)执行完后(可能要一定时间), 将任务队列中的微任务队列里的微任务依次执行(清空);
  4. 当微任务队列中的所有微任务执行完成之后,再去取出任务队列中的下一个宏任务进行执行。

由于宏任务或微任务再执行的过程中又会产生新的异步事件回调,这些回调又会被放入任务队列中。如此往复循环。称这个过程为事件循环

node的Event Loop

Event Loop分为六个阶段:

  1. timer:执行timer(setTimeout, setInterval)的回调
  2. pending callbacks:系统操作的回调
  3. idle,pepare:内部使用
  4. poll: 等待新的I/O事件
  5. check:执行setImmediate回调
  6. close callbacks:内部使用,会执行一个soket.onclose()的操作

每一个节点都有一个callbacks的先进先出的队列需要执行。当event loop运行到一个指定阶段时,该阶段的所有callbacks队列会被执行,当队列callbacks执行完或者执行的callback数量超过该阶段的上限时, event loop 会转入下一个阶段。

其中我们关心的阶段主要是 poll阶段, timer阶段, check阶段。

node event Loop主要流程

  1. 代码进入poll阶段
  2. 判断poll队列为空或受到限制,false->执行poll队列中的callback.
  3. 判断poll队列为空或受到限制, true->是否设置了setImmedidate callback true->进入check阶段。
  4. 是否设置了setImmedidate callback false->等待callback加入poll队列; 判断timer队列中的回调是否到时 true->进入timer阶段
  5. 判断timer队列中的回调是否到时 false->等待callback加入poll队列;

如下图:

未命名文件.jpg

示例

const fs = require("fs"); //进入poll
fs.readFile(__filename, () => {
    setTimeout(()=>{
        console.log("settimeout");
    }, 0);
    setImmediate(()=>{
        console.log("setImmediate");
    });
})

从上图可以看出check callback会比timer队列先执行。所以上面结果输出顺序: setImmediate , settimeout

process.nextTick()

是一个异步的node API, 但不属于event loop的阶段。

当你调用process.nextTick()时,event loop会停下来(被阻塞)先去把process.nextTick()的回调函数执行之后,再去执行事件循环。

示例

const fs = require("fs");
fs.readFile(__filename, () => {
    setTimeout(()=>{
        console.log("settimeout");
    }, 0);
    setImmediate(()=>{
        console.log("setImmediate");
        process.nextTick(()=>{
            console.log("process.nextTick1");
        });
    });
    process.nextTick(()=>{
        console.log("process.nextTick2");
    });
})

上面代码的结果输出顺序为: process.nextTick2, setImmediate , process.nextTick1 , settimeout