宏任务微任务是什么?一文读懂JS工作流程和事件循环

我们知道JavaScript是单线程,那它的工作流程到底是怎样的呢?

JS引擎

我们用的最多的就是谷歌的V8引擎。

内存堆

当我们写let a = '123'时,引擎会在内存分配一个位置存放这个变量,这个区域叫内存堆Memory Heap

调用栈

当我们调用function printA () { console.log(a) }时,引擎会把这个要执行的代码放入栈中,这个地方叫调用栈Call Stack

调用栈中最后入栈的代码会最先执行,执行完后会被移除。

因此这段代码的执行顺序是:printA入栈 -> console.log入栈 -> 执行console.log -> console.log出栈 -> printA出栈,这段代码就执行完成了。

当我们写了个死循环时,调用栈中函数的调用次数超过了调用栈的大小,我们会看到这样的错误:

Web API

其中console是浏览器提供的Web API,还有比如DOMAJAXsetTimeout都属于Web API

异步

如果调用栈中的函数需要长时间执行,那么浏览器会被阻塞,于是引入了异步的概念,异步函数中有一个回调函数,意思是,当异步函数执行和其他阻塞结束后再执行回调函数。AJAXsetTimeout就是这样的。

事件循环

有了异步函数,事件循环就是用来监控调用栈和回调队列,调度执行回调函数的。

回调队列Callback Queue

比如我在某个元素上绑定了onClickonMouseEnter的回调函数:

js 复制代码
function onMouseEnter() {}
function onClick() {}

document.addEventListener('click', onMouseEnter);
document.addEventListener('click', onClick);

在浏览器中我的鼠标进入了这个元素的区域并点击了这个元素 ,那么Web API会把这两个回调函数按鼠标的行为顺序放入回调队列中.

当调用栈为空时,事件循环会从回调队列中取第一个进入调用栈准备执行。事件循环遍历一遍回调队列称为一个tick

当我们写setTimeout(myCallback, 5000),其实意味着5秒后Web API会把myCallback放入回调队列,而只有当调用栈为空时,myCallback才会被事件循环放入调用栈执行,因此如果调用栈有阻塞或回调队列中还有微任务Micro Task时,myCallback都会等待更长时间才会被执行。

任务队列Task Queue

ES6引入了任务队列的概念,也就是升级版的回调队列。任务队列中有宏任务队列MacroTask Queue和微任务队列MicroTask Queue,事件循环会先执行当前宏任务队列,执行完后检查宏任务当中是否生成有微任务队列,执行完微任务队列后后再取下一个宏任务执行。

宏任务Macro Task

宏任务是颗粒度较大的任务,包括:

  • 所有同步任务
  • I/O操作,如文件读写、数据库数据读写等
  • setTimeoutsetInterval
  • setImmediate(Node.js环境)
  • requestAnimationFrame
  • 事件监听回调函数等
  • ...

微任务Micro Task

微任务是颗粒度较小,优先级较高的任务,包括:

  • Promisethencatchfinally
  • async/await中的代码
  • Generator函数
  • MutationObserver
  • process.nextTick(Node.js 环境)
  • ...

举例

下面我们在某个元素上绑定onClickonMouseUp,并在这两个回调函数中都加入异步函数:

js 复制代码
function onMouseUp() {
    setTimeout(() => { 
        console.log(1); 
    }, 0);
    console.log(2)
}
function onClick () {
    console.log(3);
    new Promise(resolve => {
        console.log(4);
    }).then(() => {
        console.log(5);
    });
}

要想知道console的顺序,我们先在代码中做出标记,参考注释:

js 复制代码
function onMouseUp() {       // <=== 回调函数,宏任务
    setTimeout(() => {       // <=== 回调函数中的异步函数,宏任务
        console.log(1);      // <=== 回调函数中的同步函数,宏任务
    }, 0);
    console.log(2)           // <=== 回调函数中的同步函数,宏任务
}

function onClick () {        // <=== 回调函数,宏任务
    console.log(3);          // <=== 回调函数中的同步函数,宏任务
    new Promise(resolve => { // <=== 回调函数中的同步步函数,宏任务(新建promise是宏任务)
        console.log(4);      // <=== 回调函数中的同步函数,宏任务
        resolve();
        console.log(5);
    }).then(() => {          // <=== promise中的then是微任务
        console.log(6);      // <=== 回调函数中的同步函数,宏任务
    });
    console.log(7);
}
  1. 首先浏览器上我们点击那个元素,dom会先处理mouseUp行为再处理click行为:
  1. 由此我们可以得出一个任务队列:
  1. Web API会根据setTimeout创建一个timer:
  1. 任务队列中的任务会被依次入调用栈-执行-出调用栈:

此时console台有:2,3.

  1. new Promise中的回调是立即执行的,是同步函数,而then中的回调是异步函数,是微任务,此时任务队列如下:

此时console台有:2,3.

  1. 任务队列的任务依次执行后,timer中的任务将最后执行:

此时console台有:2,3,4,5,7,6.

  1. 随着最后一个任务执行,console出的数字依次为:2,3,4,5,7,6,1。

参考

相关推荐
Y.O.U..1 分钟前
八股-2025.10.24
面试
你的电影很有趣5 分钟前
lesson77:Vue组件开发指南:从基础使用到高级通信
javascript·vue.js·1024程序员节
我是华为OD~HR~栗栗呀21 分钟前
华为OD-Java面经-21届考研
java·c++·后端·python·华为od·华为·面试
Mr.Jessy25 分钟前
JavaScript学习第六天:函数
开发语言·前端·javascript·学习·html·1024程序员节
code_YuJun1 小时前
管理系统——应用初始化 Loading 动画
前端
oak隔壁找我1 小时前
JavaScript 模块化演进历程:问题与解决方案。
前端·javascript·架构
Elieal1 小时前
AJAX 知识
前端·ajax·okhttp
sulikey1 小时前
Qt 入门简洁笔记:从框架概念到开发环境搭建
开发语言·前端·c++·qt·前端框架·visual studio·qt框架
烟袅2 小时前
JavaScript 变量声明报错指南:var、let、const 常见错误解析
javascript
烟袅2 小时前
告别 var!深入理解 JavaScript 中 var、let 和 const 的差异与最佳实践
javascript·面试