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

参考

相关推荐
罗_三金7 分钟前
前端框架对比和选择?
javascript·前端框架·vue·react·angular
Redstone Monstrosity13 分钟前
字节二面
前端·面试
东方翱翔21 分钟前
CSS的三种基本选择器
前端·css
Fan_web43 分钟前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
叫我:松哥1 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby
UestcXiye1 小时前
面试算法题精讲:求数组两组数差值和的最大值
面试·数据结构与算法·前后缀分解