js事件循环机制(event loop) 同步任务 异步任务 宏任务 微任务 任务队列与调用栈

1 js事件循环机制简单介绍

        JavaScript 是单线程的,处理耗时任务时会阻塞程序执行。为解决这一问题,引入了异步任务机制。同步任务由 JavaScript 引擎直接处理,而异步任务则由宿主环境(如 Node.js、浏览器等多线程环境)执行。同步任务会立即加入执行栈并等待结果,而异步任务则在适当时机(例如定时器结束或点击事件发生时)被放入任务队列。当执行栈中的任务完成后,事件循环会检查任务队列中是否有待处理的任务,并将其逐一加入执行栈,循环往复,这便构成了事件循环机制。

 1.1 宏任务与微任务队列

        异步任务队列又被分为了两种不同的类型队列,分别是宏任务(Macrotasks)和微任务(Microtask)任务队列。

宏任务

        宏任务是较大粒度的任务,通常对应于整体事件或动作。每个事件循环周期(Event Loop Cycle)中,事件循环会从宏任务队列中取出一个任务并执行(其实我们顶层的整个script也是一个宏任务)。

常见的宏任务类型
  • script代码块(我们的脚本也是一个宏任务)
  • setTimeout 和 setInterval:用于设置定时器,延迟执行代码。
  • I/O 操作:如文件读取、网络请求等(在Node.js中)。
  • UI 渲染事件:如 resize、scroll 等浏览器事件。
  • 用户交互事件:如 click、keydown 等。

微任务

         微任务是较小粒度的任务,通常用于在当前宏任务执行完毕后、下一个宏任务开始前执行一些需要尽快完成的操作。微任务队列的优先级高于宏任务队列。

常见的微任务类型
  • Promise 的回调:如 then、catch、finally。
  • MutationObserver:用于监测DOM变化。
  • process.nextTick(Node.js特有):用于在当前操作完成后立即执行。
  • async 和 await 特别是await后面的代码可以看作callback放入微任务队列

 事件循环机制中的执行顺序

  1. 从宏任务队列中取出第一个宏任务并执行。
  2. 执行所有微任务队列中的任务,直到微任务队列为空。
  3. 进行渲染(如果需要)。
  4. 重复以上步骤,进入下一个事件循环周期。

1.2 事件循环机制举例说明

        下面看一下示例代码1: 

// 示例代码01
console.log('script start');

setTimeout(() => {
    console.log('setTimeout');
}, 0)

console.log('script end');

        输出结果如下所示:

script start
script end
setTimeout
  1.  首先执行整个script,console.log是同步的,直接输出script start
  2.  setTimeout是新的宏任务,放入宏任务队列,继续执行余下script代码
  3.  console.log是同步的,直接输出script end
  4.  执行栈为空,微任务队列为空,取出宏任务的回调函数进行执行
  5.  输出setTimeout

         下面看一下示例代码2,我们将引入微任务:

// 示例代码02
console.log('script start')

const p = new Promise((resolve)=>{
    console.log('promise executor')
    resolve(2)
    console.log('promise executor end')
})

p.then((res) => {
    console.log(res)
})

setTimeout(() => {
    console.log('setTimeout')
})

console.log('script end')

        输出结果如下所示:

script start
promise executor
promise executor end
script end
2
setTimeout
  1. 首先输出同步内容,script start,在创建Promise对象时传入的回调函数也是同步对象,会立即执行,输出promise excutor promise executor end。
  2. .then 和 .catch都是异步的微任务,将其放入微任务队列。
  3. setTimeout是宏任务队列,放入宏任务队列。
  4. 最后将console.log放入执行栈执行,输出script end,当前宏任务执行完毕,查看微任务队列。
  5. 取出微任务队列的then函数的回调函数,执行输出 2 ,微任务队列执行完毕,取出下一个宏任务执行。
  6. 执行宏任务队列中的setTimeout输出setTimeout

        下面看最复杂的示例代码3:

// 示例代码03
async function async1() {
    console.log('async1 start')

    new Promise((resolve) => {
        resolve('promise1')

        setTimeout(() => {
            resolve('setTimeout 2')
            console.log('setTimeout 2')
        }, 0)

        new Promise((resolve) => {
            
            setTimeout(() => {
                resolve('setTimeout 3')
                console.log('setTimeout 3')
            }, 0)
        }).then((res) => {
            console.log(res)
        })
    }).then((res) => {
        console.log(res)
    })
    await async2()
    console.log('async1 end')
}

async function async2() {
    console.log('async2')
}

console.log('script start')

setTimeout(()=>{
    console.log("setTimeout 1")
}, 0)

async1()
Event loop 1
  1.  执行script,首先console.log放入执行栈输出 script start
  2. 宏任务setTimeout放入宏任务队列, 遇到异步方程,继续执行
  3. 执行异步函数async 1,首先输出async1 start进入后执行第一个Promise的回调函数,将setTimeout放如宏任务队列。
  4. 进入第二个嵌套的Promise的回调函数,添加setTimeout[setTimeout 3]
  5. 添加第二个Promise的then回调函数到微任务队列
  6. 碰到await 直接执行右侧函数,console.log放入执行栈输出 async2
  7. await之后的代码被视为then回调中的函数,放入微任务队列。

微任务队列:then(pending) then(promise 1) await(...async1 end)

宏任务队列:setTimeout[setTimeout 1] setTimeout[setTimeout 2] setTimeout[setTimeout 3]

Event loop 2
  1. 优先执行微任务,第一个微任务处于pending状态跳过,执行第一个promise的回调,输出promise1
  2. 执行await后的余下代码,输出async1 end,微任务执行完毕,执行下一个宏任务
  3. 取出setTimeout[setTimeout 1],输出setTimeout 1
  4. 宏任务结束,进入下一个事件循环

微任务队列:then(promise 2 pending)

宏任务队列:setTimeout[setTimeout 2] setTimeout[setTimeout 3]

 Event loop 3
  1. 微任务队列中的then回调依然处于pending,等待resolve触发执行,取出setTimeout [setTimeout 2] 执行(注意多次调用resolve是无效的),输出setTimeout 2
  2. 宏任务执行完毕,该循环结束。

微任务队列:then(promise 2 pending)

宏任务队列:setTimeout[setTimeout 3]

Event loop 4
  1. 微任务队列中的then回调依然处于pending,等待resolve触发执行,取出setTimeout[setTimeout 3]的回调函数执行。
  2. resolve("setTimeout")将会触发then的callback的执行,输出setTimeout 3
  3. console.log输出setTimeout 3
  4. 任务队列结束,代码执行完毕

微任务队列:empty

宏任务队列:empty

最终输出结果 
  1.  script start
  2.  async1 start
  3.  async2
  4.  promise1
  5.  async1 end
  6.  setTimeout 1
  7.  setTimeout 2
  8.  setTimeout 3
  9.  setTimeout 3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

It'sMyGo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值