由浅入深-宏任务与微任务

宏任务和微任务的文章很多,但是很多比较易理解的文章讲的不完全,而讲的完全的文章基础差又不好上手,本篇文章的目的是即容易理解又可以带你彻底搞懂这个知识点。

## 一、快速掌握
首先需要明确js是一门单线程语言,这意味着执行js中的任务只能按顺序一件一件来执行,只有当上一个任务结束后,才会去执行下一个任务,不可同时执行多个任务。

Javascript语言将任务的执行模式分成两种:同步任务和异步任务(对于这两种任务这里不再赘述,不了解的小伙伴随意百度便知),`宏任务和微任务都属于异步任务`。

**宏任务**:`script(理解为外层同步代码)`、`setTimeout/setInterval`、`postMessage`、`requestAnimationFrame`、`MessageChannel`、`UI rending/UI事件`、`setImmediate与I/O(Node.js环境)`。

**微任务**:`Promise.then()`、`await后面的代码`、 `MutaionObserver(html5新特性)`、`proxy`、`process.nextTick(Node.js)`。

在代码中的执行顺序,`同步任务>微任务>宏任务`

仅通过上面的文字可能不容易理解,让我们配合几道题来理解一下:</br>
**牛刀小试**: consloe.log()会输出什么呢

```
console.log(1)

setTimeout(() => {
    console.log(4)
}, 0);

new Promise(resolve=>{
    console.log(5)
    resolve(3)
}).then(res=>{
    console.log(res)
})

console.log(2)
```
`答案:15234`</br>
`同步任务:152`</br>
`微任务:3`</br>
`宏任务:4`</br>
这里只有一个需要注意的地方就是Promise,`.then方法才是微任务`,console.log(5)是同步任务

哈哈,先同步任务再微任务最后宏任务,这道题一定难不住聪明的你吧,提升一下难度!

```
console.log(1);

setTimeout(() => {
    console.log(4);
}, 0);

new Promise((resolve) => {
    console.log(5);
    resolve(3);
    new Promise((resolve) => {
        console.log(6);
        resolve(7);
    }).then((res) => {
        console.log(res);
    });
}).then((res) => {
console.log(res);
});

console.log(2);
```
`答案:1562734`</br>
`同步任务:1562`</br>
`微任务:73`</br>
`宏任务:4`</br>
这里需要注意的地方是Promise.then()这个微任务放到事件队列的顺序,`并不是执行到resolve()方法就会将这个微任务加入到事件队列,而是要执行到.then方法的才会被加入事件队列`,所以6在前而7在后(ps:事件队列可以理解为要被执行的微任务会被加入到事件队列,先进先出)

这道题你答对了吗?再看下一道!
```
setTimeout(() => {
    console.log(1);
}, 0);
async function test1() {
    console.log(2)
}
async function test2() {
    await test1()
    console.log(3)
}
test2()

new Promise((resolve) => {
    console.log(4);
    resolve(5);
    new Promise((resolve) => {
        console.log(6);
        resolve(7);
    }).then((res) => {
        console.log(res);
    });
}).then((res) => {
console.log(res);
});
```
`答案:2463751`</br>
`同步任务:246`</br>
`微任务:357`</br>
`宏任务:1`</br>
这里需要注意的地方是await,`await后面一般跟一个Promise对象,如果后面不是Promise对象就会被转成Promise对象,await会暂停当前async function的执行,等待await表达式后面的Promise处理完成后才会继续执行。`

最后再来一道UI事件相关的!</br>
下面这道题如果用户在页面加载后就去点击按钮,console.log()的输出顺序又是怎样的呢?
```
document.onclick = function() {
    console.log("1");
};
console.log(2);

// 3秒之后才会提交到任务队列中
setTimeout(function() {
    console.log(3);
}, 3000);

setTimeout(() => {
    console.log(4);
}, 0);
```
`答案:2413`</br>
`同步任务:2`</br>
`微任务:41`</br>
`宏任务:3`</br>
这道题就不做过多解释了,至此,想必在应用层面,你已经掌握了宏任务和微任务了,如果你只是想会使用的话到这里就可以退出了。

## 二、常见问题

#### 1.为什么说script是一个宏任务?

这里说的script是`<script></script>`脚本,这被当作是一个宏函数,可能有些人不太好理解,我们来看个例子:</br>
请写出下面这个例子中console.log()的输出顺序

```
<!-- 脚本 1 -->
<script>
console.log('1-1')
setTimeout(() => console.log('1-2'), 0)

new Promise((resolve, reject) => {
    console.log('1-3')
    resolve('1-4')
}).then((res) => {
    console.log(res)
})

console.log('1-5')
</script>

<!-- 脚本 2 -->
<script>
console.log('2-1')
setTimeout(() => console.log('2-2'), 0)

new Promise((resolve, reject) => {
    console.log('2-3')
    resolve('2-4')
}).then((res) => {
    console.log(res)
})

console.log('2-5')
</script>
```
`答案:1-1、1-3、1-5、1-4、2-1、2-3、2-5、2-4、1-2、2-2`</br>
这里将`script脚本`作为宏任务,执行顺序如下:
1. 将宏任务script1和script2加入event table,并按顺序注册到event queue,等待主线程拉取
2. 主线程拉取第一个宏任务script1,先执行script1中的同步任务,输出`1-1`、`1-3`、`1-5`,然后执行微任务,输出`1-4`,在执行script1的过程中发现settimeout宏任务,将settimeout加入到event table,并在大约10毫秒后推入event queue
3. 此时,script1中的同步任务和微任务都已经执行完毕了,主线程去event queue拉取先注册的宏任务script2,
4. 执行script2中的同步任务,输出`2-1`、`2-3`、`2-5`,然后是微任务,输出`2-4`,在执行script2的过程中发现settimeout宏任务,将settimeout加入到event table,并在大约10毫秒后推入event queue
5. script2中的同步任务和微任务都已经执行完毕了,主线程去event queue拉取注册靠前的宏任务script1中的settimeout,输出`1-2`,settimeout中没有微任务需要执行
6. 主线程去event queue拉取宏任务script1中的settimeout,输出`2-2`

这里对上面提到的`event table`和`event queue`做个补充解释-`JS的执行机制`:
- 首先判断JS是同步还是异步,同步就进入主进程,异步就进入event table
- 异步任务在event table中注册函数,当满足触发条件后,被推入event queue
- 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中。</br>

    满足触发条件是指什么呢?</br>
    以上面的settimeout为例:`settimeout(()=>{},10)`,表示10豪秒后可以执行回调函数,这里满足触发的条件就是10毫秒后。
    

#### 2.宏任务与微任务的理解和差异

很多人说是先微再宏,这样说没问题,但是我觉得这样不太好理解。</br>

这里多提一嘴我个人对宏任务和微任务的理解,我将宏任务比作一个大型生物(如:一颗参天大树),在这颗大树上可以生长存活其他的动物(宏任务)和寄生于这颗大树的动植物(微任务),一个名为主线程的人提着一把刀要灭绝这里(好像有点邪恶哈...)。首先刀砍大树(刀名:js引擎线程),当大树倒下了,第一时间死掉的是寄生于这颗大树的生物(微任务),然后依次再轮到其他的动物(宏任务),循环往复将这颗大树身上所有的生物都干掉!

###### 微任务宏任务差异表

|                  | 宏任务 | 微任务 |
| ---              | --- |--- |
|  是否重新渲染页面   |   会|不会|
|  是否需要其他异步线程的支持  |   需要|不需要|
|  宏任务与微任务发起者  |   宿主(node、浏览器)|js引擎|
|  具体事件  |   script、setTimeout/setInterval、postMessage、MessageChannel、requestAnimationFrame、UI rending/UI事件、setImmediate与I/O(Node.js环境)|Promise.then()、await后面的代码、 MutaionObserver(html5新特性)、proxy、process.nextTick(Node.js) |

#### 3.宏任务与微任务产生的误差
直接上代码

```
console.log(1)
new Promise((resolve,reject)=>{
    console.log(2)
    resolve(3)
}).then(res=>{
    console.log(res)
    const targetTime = new Date().getTime()+3000
    let isEnd = false

    // 延时三秒
    while(!isEnd){
        const currentTime = new Date().getTime()
        if (targetTime <= currentTime){
            isEnd = true
        }
    }
})
setTimeout(() => console.log(4), 0)
console.log(5)
```
`答案:12534`</br>
`同步任务:125`</br>
`微任务:3`</br>
`宏任务:4`</br>
`3秒后console.log(4)被输出`,因为在执行promise微任务耗费了3秒</br>
setTimeout二个参数仅仅表示最少延迟时间,而非确切的等待时间,其他具有回调方法的宏任务也基本是这样。
即使是在异步任务中的做费时等的延迟操作,也会影响到同为异步任务的宏任务,在代码开发中一定要注意比较耗时的代码所产生的影响!

#### 4.通过一个例子加深了解
下面这个例子中`alert()`和`界面div的显示的变化`是如何顺序的呢?

```
<body>
<div class="demo"></div>
<script>
window.onload = function() {
    const div = document.querySelector(".demo");

    Promise.resolve().then(() => {
        alert("promise0");
    });
    alert('同步0')

    // 宏任务1
    setTimeout(() => {
        Promise.resolve().then(() => {
            alert("promise1");
        });
        div.textContent = "元素div1";
        alert("settimeout1");
    }, 0);

    // 宏任务2
    setTimeout(() => {
        div.textContent = "元素div2";
        alert("settimeout2");
    }, 0);
};
</script>
</body>

```

`答案:`</br>
`1.弹出框'同步0'(同步任务)`</br>
`2.弹出框"promise0"(微任务)`</br>
`3.弹出框"settimeout1"(settimeout1宏任务)`</br>
`4.页面渲染:页面中的'元素div1'被渲染展示`</br>
`5.弹出框"promise1"(settimeout1宏任务中的微任务)`</br>
`6.弹出框"settimeout2"(settimeout2宏任务)`</br>
`7.页面渲染:页面中的文字'元素div1'=>'元素div2'`</br>

这个例子之所以用alert,是因为alert会阻塞线程,方便我们观察页面渲染,这个例子主要想表达:
###### 每个宏任务执行完毕并清空里面的微任务后,会进行一次页面渲染</br>

最后附赠一张网上找的事件循环的图,大家可以看图再理解一下,哪里不明白也可以私信或者评论问我

![ca805adde7a54f5aa9ffd054b290fd9e.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2009bb97b3be41bdba8ab3e4f39bd9ed~tplv-k3u1fbpfcp-watermark.image?)


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值