Event Loop——setTimeout、setInterval、Promise、nextTick

62 阅读3分钟

事件循环

众所周知,JavaScript是单线程的。为了程序不被耗时很久的任务阻断,JavaScript分了同步任务和异步任务。同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列 (Event Queue,机制为先进先出)。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环),一次Event Loop也叫做一次tick。

同步任务

同步任务是指在主线程上排队执行的任务,只有当前面的任务执行完之后,才会执行下一个任务。

异步任务

异步任务是指不进入主线程,而是放在任务队列中等待执行的任务,只有当任务队列中的任务可以执行了,才会进入主线程开始执行。setTimeout、Promise等都是异步任务。

宏任务(Macrotask)

宏任务是指需要排队等待JavaScript引擎空闲时才能执行的任务。setTimeout、setInterval、I/O操作、DOM事件等为宏任务。

微任务(Microtask)

微任务是指在当前任务执行结束后立即执行的任务(只有当没有运行中的执行上下文且执行上下文堆栈为空时才能执行微任务)。Promise、nextTick、MutationObserver等为微任务。

Event Loop流程图——用一个函数的执行过程来表示(函数中包含Promise、setTimeout)

image.png

以下面代码为例来演示一下一次Event Loop的流程
function promise1() {
    // 开始执行同步代码,入栈promise1Stack
    // (宏任务队列:script;上下文栈:promise1Stack)
    console.log('promise start...');

    // 宏任务,加入到宏任务队列
    // (宏任务队列:script/timeout1;上下文栈:promise1Stack)
    setTimeout(() => {
        console.log('timeout1');
    }, 0);

    // 微任务,加入到微任务队列
    // (宏任务队列:script/timeout1;上下文栈:promise1Stack;微任务:promise1)
    Promise.resolve('promise1').then((value) => {
        console.log(value);
    })

    // 宏任务,加入到宏任务队列
    // (宏任务队列:script/timeout1/timeout2;上下文栈:promise1Stack;微任务:promise1)
    setTimeout(() => {
        console.log('timeout2');
    }, 0);

    // 微任务
    // nextTick(() => {
    //     console.log('nextTick...');
    // });

    // 微任务,加入到微任务队列
    // (宏任务队列:script/timeout1/timeout2;上下文栈:promise1Stack;微任务:promise1/promise2)
    Promise.resolve('promise2').then(value => {
        console.log(value);
    })

    // 同步代码,同步代码执行结束,出栈promise1Stack,宏任务script执行结束
    // (宏任务队列:timeout1/timeout2;上下文栈:null;微任务:promise1/promise2)
    console.log('promise end...');
    // 开始执行微任务,先进先出原则

    // 1.执行promise1,打印promise1;
    // (宏任务队列:timeout1/timeout2;上下文栈:null;微任务:promise2)
    // 2.执行promise2,打印promise2;
    // (宏任务队列:timeout1/timeout2;上下文栈:null;微任务:null)
    // 3.执行宏任务timeout1,打印timeout1;
    // (宏任务队列:timeout2;上下文栈:null;微任务:null)
    // 4.执行宏任务timeout2,打印timeout2;
    // (宏任务队列:null;上下文栈:null;微任务:null)

    // 至此,promise1函数执行完毕,打印结果为:
    // promise start...
    // promise end...
    // promise1
    // promise2
    // timeout1
    // timeout2
}

控制台打印结果

image.png

以上就是一次Event Loop的核心流程。

:::info 后续会继续补充和优化一些内容,欢迎大家指出不足之处!!! :::