简述js的事件循环以及宏任务和微任务

前言

JavaScript中,任务被分为同步任务和异步任务。

  • 同步任务:这些任务在主线程上顺序执行,不会进入任务队列,而是直接在主线程上排队等待执行。每个同步任务都会阻塞后续任务的执行,直到它自身完成。常见的同步任务包括页面的初始化、DOM操作和某些计算任务。
  • 异步任务 :与同步任务不同,异步任务不直接进入主线程执行,而是被放入任务队列(task queue)中。只有当主线程空闲时,才会从任务队列中取出任务来执行。异步任务不会阻塞主线程的执行。根据任务类型,异步任务又被分为宏任务微任务

一、事件循环是什么?

  • JavaScript 的事件循环(Event Loop)是其运行时环境(如浏览器或 Node.js处理异步操作和回调的一种机制 。它允许 JavaScript 在不阻塞单线程执行的情况下,响应用户交互、处理网络请求、定时器回调等异步事件

二、事件循环的基本基本概念和工作原理

1. 调用栈(Call Stack

JavaScript 引擎有一个调用栈,用于跟踪函数调用的顺序。当一个函数调用发生时,它会被推入调用栈中。当函数执行完毕后,它会被从调用栈中弹出。

2. 任务队列(Task Queue):

当异步操作(如 setTimeout、setInterval、DOM 事件、Promise.resolve().then() 等)完成时,它们会生成一个任务(task)(简单理解为任务就是回调函数),并将这个任务放入相应的任务队列中等待执行。

  • 这些任务队列包括宏任务队列(macrotask queue)和微任务队列(microtask queue
  • 宏任务队列主要存放 script(全局任务)、setTimeoutsetIntervalsetImmediateNode.js 环境)等;微任务队列主要存放 Promise 的回调函数、MutationObserver(浏览器环境)等。
3. 事件循环:

事件循环的基本顺序是:

  1. 当调用栈为空时(即没有正在执行的函数),事件循环会查看任务队列。
  2. 事件循环会率先查看微任务队列。如果微任务队列中有任务,它会将任务逐个取出并执行,直到微任务队列为空。
  3. 然后,事件循环会查看宏任务队列。如果宏任务队列中有任务,它会将任务取出并执行。在执行宏任务的过程中,可能会产生新的微任务,这些微任务会被添加到微任务队列的末尾。
  4. 当一个宏任务执行完毕后,事件循环会再次查看微任务队列并执行其中的任务,这个过程会一直重复,直到所有的任务都被执行完毕。
  5. 这个过程会持续进行,形成了一个循环,这就是所谓的"事件循环"。

三、宏任务和微任务?

  • JavaScript的事件循环中,任务的执行被分为两种主要的类别:宏任务(MacroTask)和微任务(MicroTask)。这两种任务类型在事件循环中的处理顺序和方式有所不同。
1. 宏任务(MacroTask

宏任务通常包括:

  • script(整体代码)
  • setTimeout
  • setInterval
  • setImmediateNode.js 环境)
  • I/O
  • UI渲染(浏览器会在每次事件循环结束后进行UI渲染)
  • MessageChannel(消息通道)
  • postMessage(一些HTML5 API使用)
  • requestAnimationFrame(浏览器用于定时执行动画)

宏任务创建后会被放入宏任务队列中,JavaScript引擎会在当前执行栈清空后,从宏任务队列中取出队首任务执行。

2. 微任务(MicroTask

微任务通常包括:

  • Promise.then()Promise.catch()
  • MutationObserverHTML5API,用于监听DOM变更)
  • process.nextTickNode.js环境)
    与宏任务不同,微任务是在当前宏任务执行完成后立即执行的。在JavaScript引擎执行完一个宏任务后,它会先查看微任务队列,并执行所有的微任务,直到微任务队列为空。然后,它会继续取出并执行下一个宏任务。这个过程会不断重复,形成JavaScript的事件循环。

执行顺序

考虑以下的示例:

js 复制代码
javascript
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'); // 同步任务
尽管setTimeout的延迟被设置为0,但它的回调仍然会在所有的微任务之后执行。因此,上述代码的输出顺序为:

script start  
script end  
promise1  
promise2  
setTimeout
这是因为当JavaScript引擎执行到setTimeout时,它会将回调函数放入宏任务队列,并继续执行后续的代码。当执行到Promise.then()时,它会将回调函数放入微任务队列。在所有宏任务代码执行完毕后,JavaScript引擎会先执行所有的微任务,然后再从宏任务队列中取出并执行下一个宏任务。

四、练习

  1. 练习一:
js 复制代码
  console.log('Start'); // 同步任务

  // 宏任务1
  setTimeout(() => {
    console.log('Timeout callback'); // 同步任务
    Promise.resolve().then(() => {
      console.log('Promise 1'); //  微任务1
      Promise.resolve().then(() => {
        console.log('Promise 2'); //  微任务2
        Promise.resolve().then(() => {
          console.log('Promise 3'); //  微任务3执行完执行下一个宏任务
        });
      });
    });

  }, 0);

  // 宏任务2
  setTimeout(() => {
    console.log('Timeout222 callback'); // 6
  }, 0);
  1. 练习二:
js 复制代码
  //  开启一个微任务,当dom修改时触发
  const observer = new MutationObserver(function (mutationsList, observer) {
    console.log(mutationsList, observer)
  });
  const config = { attributes: true, childList: true, subtree: true };


  console.log('script start'); // 同步任务 1  

  (function () {
    console.log('自执行函数 '); // 同步任务 2
  })()

  // 宏任务2
  setTimeout(function () {
    Promise.resolve().then(function () {
      var element = document.getElementById('app');
      observer.observe(element, config);

      var child = document.getElementById('child');
      element.innerHTML = '<p>这是一段新的HTML内容。</p>';

      console.log('promise11'); // 同步任务  

      Promise.resolve().then(() => {
        console.log('promise11 callback 1'); // (3) 微任务  
      });
      Promise.resolve().then(() => {
        console.log('promise11 callback 2'); // (3) 微任务  
      });
    })
    console.log('setTimeout'); // 同步任务  

  }, 0);


  // 宏任务3
  setTimeout(() => {
    console.log(111);
  })


  Promise.resolve().then(function () {
    console.log('promise1'); // 微任务1  
    Promise.resolve().then(() => {
      console.log('promise1 callback 1'); // 微任务1-2  
    });
    Promise.resolve().then(() => {
      console.log('promise1 callback 2'); // 微任务 1-3  
    });
  }).then(function () {
    console.log('promise2'); // 微任务2  

    // 宏任务4
    setTimeout(() => {
      console.log('微任务内的宏任务'); // 宏任务队列4
      Promise.resolve().then(() => {
        console.log('微任务2 promise callback'); // 微任务队列4 
      });
    })

    Promise.resolve().then(() => {
      console.log('promise2 callback 1'); // 微任务2-1  
    });
    Promise.resolve().then(() => {
      console.log('promise2 callback 2'); // 微任务2-2  
    });

  })

  console.log('script end'); // 同步任务3


  // 执行同步任务,当遇到异步宏任务放入宏任务队列,异步微任务放入微任务队列
  // 所以执行顺序

  // script start
  // 自执行函数
  // script end
  // promise1
  // promise1 callback 1
  // promise1 callback 2
  // promise2
  // promise2 callback 1
  // promise2 callback 2
  // ---宏任务2
  // setTimeout
  // promise11
  // MutationObserver
  // promise11 callback 1
  // promise11 callback 2
  // ---宏任务3
  // 111
  // ---宏任务4
  // 微任务内的宏任务
  // 微任务2 promise callback
  1. 练习三:script整体为何是宏任务
js 复制代码
// 宏任务一
<script>
  console.log('script1') // 同步

  // 宏任务三
  setTimeout(() => {
    console.log('setTimeout1');
    // 宏任务五
    setTimeout(() => {
      console.log('setTimeout3');
    })
  })

  // 微任务
  Promise.resolve().then(() => {
    console.log('promise1');
  })
</script>

// 宏任务二
<script>
  console.log('script2') // 同步

  // 宏任务四
  setTimeout(() => {
    console.log('setTimeout2');

    // 宏任务六
    setTimeout(() => {
      console.log('setTimeout4');
    })
  })

  // 微任务
  Promise.resolve().then(() => {
    console.log('promise2');
  })
</script>

可以看出来script相当于setTimeOut开启宏任务列表,执行完当前宏任务去执行微任务,微任务执行完毕,执行宏任务二,以此类推

所以输出结果:
script1
promise1
script2
promise2
setTimeout1
setTimeout2
setTimeout3
setTimeout4
相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端