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

参考

相关推荐
在未来等你2 小时前
Elasticsearch面试精讲 Day 17:查询性能调优实践
大数据·分布式·elasticsearch·搜索引擎·面试
人工智能训练师4 小时前
Ubuntu22.04如何安装新版本的Node.js和npm
linux·运维·前端·人工智能·ubuntu·npm·node.js
Seveny074 小时前
pnpm相对于npm,yarn的优势
前端·npm·node.js
yddddddy5 小时前
css的基本知识
前端·css
昔人'5 小时前
css `lh`单位
前端·css
前端君5 小时前
实现最大异步并发执行队列
javascript
围巾哥萧尘6 小时前
美式审美的商务头像照🧣
面试
Nan_Shu_6146 小时前
Web前端面试题(2)
前端
知识分享小能手7 小时前
React学习教程,从入门到精通,React 组件核心语法知识点详解(类组件体系)(19)
前端·javascript·vue.js·学习·react.js·react·anti-design-vue
蚂蚁RichLab前端团队8 小时前
🚀🚀🚀 RichLab - 花呗前端团队招贤纳士 - 【转岗/内推/社招】
前端·javascript·人工智能