宏任务微任务是什么?一文读懂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。

参考

相关推荐
青皮桔28 分钟前
CSS实现百分比水柱图
前端·css
失落的多巴胺28 分钟前
使用deepseek制作“喝什么奶茶”随机抽签小网页
javascript·css·css3·html5
DataGear31 分钟前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
影子信息33 分钟前
vue 前端动态导入文件 import.meta.glob
前端·javascript·vue.js
青阳流月34 分钟前
1.vue权衡的艺术
前端·vue.js·开源
样子201838 分钟前
Vue3 之dialog弹框简单制作
前端·javascript·vue.js·前端框架·ecmascript
kevin_水滴石穿39 分钟前
Vue 中报错 TypeError: crypto$2.getRandomValues is not a function
前端·javascript·vue.js
翻滚吧键盘39 分钟前
vue文本插值
javascript·vue.js·ecmascript
孤水寒月2 小时前
给自己网站增加一个免费的AI助手,纯HTML
前端·人工智能·html
CoderLiu2 小时前
用这个MCP,只给大模型一个figma链接就能直接导出图片,还能自动压缩上传?
前端·llm·mcp