js中的事件循环机制(宏任务和微任务)

JavaScript的事件循环机制是其非阻塞I/O模型的核心,它使得JavaScript能够在单线程环境中高效地处理异步操作。事件循环机制主要由以下几个部分组成:

  1. 调用栈(Call Stack)

    • 这是JavaScript执行同步代码的地方,后进先出(LIFO)的数据结构。
    • 当一个函数执行时,它会被推入栈顶,执行完毕后从栈顶弹出。
  2. 事件队列(Event Queue)

    • 也称为任务队列,用于存放异步操作的回调函数。
    • 事件队列可以有多个,但最常见的是宏任务队列和微任务队列。
  3. 宏任务(Macrotasks)

    • 包括如setTimeoutsetInterval、I/O操作、网络请求、UI渲染等
    • 每个宏任务执行完毕后,会检查并执行所有微任务队列中的微任务
  4. 微任务(Microtasks)

    • 包括Promise.then().catch().finally(),以及MutationObserver。(注意区分:new Promise,Promise构造函数是同步执行的)
    • 微任务的优先级高于宏任务,当调用栈清空后,会立即执行所有微任务队列中的微任务。
  5. 事件循环(Event Loop)

    • 事件循环是JavaScript运行时的调度机制,它不断地检查调用栈和事件队列。
    • 当调用栈清空时,事件循环会从宏任务队列中取出第一个任务执行,然后执行所有微任务队列中的微任务,接着检查是否需要进行UI渲染,然后再次检查宏任务队列。

事件循环的工作流程:

  1. 执行同步代码:同步代码在调用栈中执行,直到调用栈清空。
  2. 执行宏任务:调用栈清空后,事件循环从宏任务队列中取出第一个任务执行,将其推入调用栈。
  3. 执行微任务:宏任务执行完毕后,事件循环会立即执行所有微任务队列中的微任务,直到微任务队列清空。
  4. UI渲染:如果需要,浏览器会进行UI渲染更新。
  5. 重复循环下一个宏任务:如果宏任务队列中还有任务,事件循环会再次开始,执行下一个宏任务,直到调用栈和事件队列都为空。
console.log('Script start');

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

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('Script end');

 

执行顺序如下:

  1. 执行同步代码 console.log('Script start')
  2. 执行同步代码 setTimeout(function() { console.log('setTimeout'); }, 0);。这个宏任务被添加到宏任务队列。
  3. 执行同步代码 Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); });。这两个微任务被添加到微任务队列。
  4. 执行同步代码 console.log('Script end')
  5. 同步代码执行完毕,开始执行微任务队列中的所有微任务。首先执行第一个微任务 console.log('promise1')
  6. 执行第二个微任务 console.log('promise2')
  7. 微任务队列清空,执行渲染更新(如果有的话)。
  8. 检查宏任务队列,执行 setTimeout 中的回调函数 console.log('setTimeout')

输出结果:

Script start

Script end

promise1

promise2

setTimeout 

这道题会做了,应该就彻底理解了:

setTimeout(() => {
      console.log('1');
      new Promise(function (resolve, reject) {
        console.log('2');
        setTimeout(() => {
          console.log('3');
        }, 0);
        resolve();
      }).then(function () {
        console.log('4')
      })
    }, 0);
    
    console.log('5'); 
    setTimeout(() => {
      console.log('6');
    }, 0);
    new Promise(function (resolve, reject) {
      console.log('7');
      reject();
      resolve();
    }).then(function () {
      console.log('8')
    }).catch(function () {
      console.log('9')
    })
    console.log('10');

 输出结果:// 5 7 10 8 1 2 4 6 3

分析:

  1. setTimeout(() => { console.log('1'); ... }, 0); 被推入宏任务队列,因为 setTimeout 是一个宏任务。

  2. console.log('5'); 执行,输出 '5'。

  3. setTimeout(() => { console.log('6'); }, 0); 被推入宏任务队列。

  4. new Promise(...) 创建了一个 Promise 实例,其执行器函数立即执行(因为Promise构造函数是同步执行的),console.log('7'); 输出 '7'。

  5. resolve(); 在Promise的执行器函数中被调用,这将Promise状态变为resolved,并将 .then(function () { console.log('8') }) 中的回调函数推入微任务队列。

  6. console.log('10'); 执行,输出 '10'。

至此,主线程中的同步代码执行完毕,事件循环开始处理微任务队列:

  1. 微任务队列中的 .then(function () { console.log('8') }) 执行,输出 '8'。

  2. 由于没有其他的微任务,事件循环开始处理宏任务队列中的下一个宏任务:

    • 首先执行 setTimeout(() => { console.log('1'); ... }, 0); 中的回调:
      • console.log('1'); 输出 '1'。
      • new Promise(...) 创建并立即执行执行器函数中的代码:
        • console.log('2'); 输出 '2'。
        • 内部的 setTimeout(() => { console.log('3'); }, 0); 被推入宏任务队列。
        • resolve(); 被调用,.then(function () { console.log('4') }) 中的回调函数被推入微任务队列。
      • console.log('4') 由于 resolve() 调用后立即推入微任务队列,输出 '4'。
  3. 事件循环再次处理微任务队列,输出 '4'。

  4. 然后,事件循环处理宏任务队列中的下一个宏任务,执行 setTimeout(() => { console.log('6'); }, 0); 中的回调,输出 '6'。

  5. 由于宏任务队列中还有之前由 console.log('2'); 内部创建的 setTimeout(() => { console.log('3'); }, 0);,事件循环最终处理这个宏任务,输出 '3'。

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值