JavaScript 的宏任务和微任务

文章目录

        • [1. 前言](#1. 前言)
        • [2. 同步任务](#2. 同步任务)
        • [3. 异步任务](#3. 异步任务)
        • [4. 定时器的任务编排](#4. 定时器的任务编排)
        • [5. Promise 微任务处理逻辑](#5. Promise 微任务处理逻辑)
        • [6. DOM 渲染任务](#6. DOM 渲染任务)
        • [7. 任务共享内存](#7. 任务共享内存)
        • [8. 进度条实例体验任务轮询](#8. 进度条实例体验任务轮询)
        • [9. 任务拆分为多个子任务](#9. 任务拆分为多个子任务)
        • [10. Promise 微任务处理复杂逻辑](#10. Promise 微任务处理复杂逻辑)
1. 前言

在 JavaScript 中,任务可以分为同步任务、异步任务,而异步任务又进一步细分为宏任务和微任务

  • 同步任务:指那些在主线程上排队执行的任务,只有前一个任务执行完毕后,才能开始执行下一个任务
  • 异步任务:异步任务不会进入主线程,而是进入 "任务队列" 中等待执行

JavaScript 语言本身是单线程,这意味着在任何时刻,JavaScript 引擎中只有一条主线程来处理所有的任务

2. 同步任务

同步任务只有前一个任务执行完毕后,才能开始执行下一个任务,特点是按照顺序执行,会阻塞后续代码的执行

javascript 复制代码
console.log(1);
console.log(2);
3. 异步任务

异步任务是只有当主线程上的同步任务全部执行完毕后,才会从任务队列中取出异步任务进入主线程执行

javascript 复制代码
console.log(1); // 同步任务
setTimeout(() => console.log(3), 0) // 异步任务,等待同步任务执行完毕之后,才会执行
console.log(2); // 同步任务
宏任务

宏任务是由宿主环境发起的异步任务,通常包含较长的等待时间或需要等待 I/O 操作完成

常见的宏任务:setTimeout / setInterval、ajax 请求、dom 事件

微任务

微任务通常用于处理 Promise 或其他需要尽快执行的操作

常见的微任务:Promise.then、async / await

javascript 复制代码
setTimeout(() => console.log('定时器 - 宏任务'), 0)
Promise.resolve().then(value => {
    console.log('Promise - 微任务');
})
console.log('123 - 同步任务')
// 123 - 同步任务
// Promise - 微任务
// 定时器 - 宏任务
4. 定时器的任务编排

在 JavaScript 中,setTimeout 函数的延时时间的最小值通常是 4 毫秒

这是因为在某些浏览器或操作系统中,为了优化性能和节能,定时器的实际执行可能会有一定的最小延迟

javascript 复制代码
// 即使设置了非常小的延时,浏览器也可能不会立即执行定时器回调函数,而是等待至少 4 毫秒后才执行
setTimeout(() => console.log('定时器'), 0)

设置定时器延时 200 毫秒执行,那么它真的就会 200 毫秒后立即执行吗 ?通过以下代码可以发现并不是这样的

代码解析原因:因为定时器是异步任务,for 循环是同步任务,所以定时器要等 for 循环执行完之后才会执行定时器

定时器的任务编排原理:到达延时时间会先将其放到宏任务队列中,等待主线程处理完同步任务,再来处理异步任务

javascript 复制代码
console.log(new Date().getTime() / 1000);
setTimeout(() => {
    console.log('定时器:' + new Date().getTime() / 1000);
}, 200)
console.log('同步任务')
for (let i = 0; i < 20000; i++) {
    console.log('');
}
5. Promise 微任务处理逻辑

Promise 是微任务中的典型代表,Promise 的构造函数代码是立即执行的,也就是说它是同步代码

javascript 复制代码
// 4. 定时器,放入宏任务队列
setTimeout(() => console.log('定时器'))

new Promise(resolve => {
    // 1. 这里是 promise 的构造函数,它是同步代码
    console.log('promise');
    resolve()
}).then(() => {
    // 3. 这里的代码会放到微任务队列
    console.log('then');
})

// 2. 同步代码
console.log('hello');

// 由于任务执行顺序是:同步任务 > 微任务 > 宏任务,所以上面代码的输出顺序为
// promise
// hello
// then
// 定时器

如果在定时器内部再放入 Promise 和 定时器,那么执行顺序是怎么样的 ?

javascript 复制代码
setTimeout(() => {
    // 1. 同步代码
    console.log('定时器')
    // 因为它是在定时器内部,所以它在宏任务执行之后才会放入任务队列,才会执行
    new Promise(resolve => {
        // 2. 同步代码
        console.log('timeout promise');
        resolve()
    }).then(() => {
        // 3. 微任务
        console.log('timeout then');
    })
    // 4. 宏任务
    setTimeout(() => console.log('定时器内部的定时器'))
})
new Promise(resolve => {
    console.log('promise');
    resolve()
}).then(() => {
    console.log('then');
})
console.log('hello');

// 其实很简单,在上一段代码的结果上,继续按照任务执行优先顺序执行
// promise
// hello
// then
// 定时器
// timeout promise
// timeout then
// 定时器内部的定时器
6. DOM 渲染任务

浏览器在解析页面时,也是按照顺序从上往下解析的,如下所示,先解析 utils.js,然后再渲染 h1 标签

也可以理解为先将解析 utils.js 的任务执行完,再执行渲染 h1 标签的任务

html 复制代码
<head>
    <script src="./utils.js"></script>
</head>
<body>
    <h1>JavaScript 的宏任务和微任务</h1>
</body>

如果执行 utils.js 的时间比较长,那么页面将迟迟不渲染,进入页面会有较长时间的空白内容

javascript 复制代码
for (let index = 0; index < 5000000; index++) {
    document.write(' ')
}

我们可以将 utils.js 的引入放到内容渲染后面,改变任务的执行顺序,进入页面就不会有长时间的空白了

html 复制代码
<body>
    <h1>JavaScript 的宏任务和微任务</h1>
    <script src="./utils.js"></script>
</body>
7. 任务共享内存

任务共享内存是指多个任务可以访问同一块内存区域

多个定时器设置相同时间延时执行,它们并不会同时执行,而是到达设定的时间后将它们放入任务队列,依次执行任务

javascript 复制代码
let i = 0
setTimeout(() => {
    // 宏任务
    console.log(++i);
}, 1000)
setTimeout(() => {
    // 宏任务
    console.log(++i);
}, 1000)
8. 进度条实例体验任务轮询

先写好一个进度条的样式

html 复制代码
<head>
    <style>
        .container {
            width: 200px;
            border-radius: 3px;
            border: 1px solid #ccc;
        }

        #progress {
            width: 0%;
            color: #fff;
            text-align: center;
            background-color: #376de1;
        }
    </style>
</head>

<body>
    <div class="container">
        <div id="progress">0</div>
    </div>
</body>

定时器其实就是往任务队列中不断的放入任务,到达执行时间后,交由主线程依次执行任务

javascript 复制代码
function handle() {
    let i = 0;
    (function run() {
        if (++i <= 100) {
            progress.innerHTML = i
            progress.style.width = i + '%'
            setTimeout(run, 50)
        }
    })();
}
handle()
9. 任务拆分为多个子任务

现有以下代码,前面的累加函数需要较长时间才能执行完成,阻塞了后面代码的运行,我们来看一下怎么解决这个问题

javascript 复制代码
let count = 0
let num = 987654321
function task() {
    for (let i = 0; i < num; i++) {
        count += num--
    }
    console.log(count);
}
task() // 同步任务
console.log('hello'); // 同步任务,需要等待 task() 执行完后才能执行

思路是将大量的计算放入异步任务,让同步任务先执行,以免造成阻塞

javascript 复制代码
let count = 0
let num = 10000000
function task() {
    // 首次调用先累加一次
    for (let i = 0; i <= num; i++) {
        if (num <= 0) break;
        count += num--
    }
    if (num > 0) {
        // 后续累加,放入宏任务执行
        setTimeout(task)
    } else {
        console.log(count)
    }
}
task()
console.log('hello')
10. Promise 微任务处理复杂逻辑

将定时器放入 Promise 也可以处理复杂逻辑,不影响同步任务的执行,但下面用的还是宏任务(定时器)

javascript 复制代码
let count = 0
let num = 987654321

// 由于 promise 里面的代码是同步执行的,在里面直接写循环还是会卡住,所以可以写个定时器
function task() {
    return new Promise(resolve => {
        let count = 0
        setTimeout(() => {
            for (let i = 0; i < num; i++) {
                count += num--
            }
            resolve(count)
        })
    });
}
async function getSum(num) {
    let res = await task(num)
    console.log(res);
}
getSum(num)
console.log('hello');

因为 Promise 的 then 回调方法是微任务,那么我们可以将代码调整为:

javascript 复制代码
let num = 987654321
async function getSum(num) {
    let res = await Promise.resolve().then(_ => {
        let count = 0
        for (let i = 0; i < num; i++) {
            count += num--
        }
        return count
    })
}
getSum(num)
console.log('hello');
相关推荐
冰暮流星6 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_6 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
hedley(●'◡'●)7 小时前
基于cesium和vue的大疆司空模仿程序
前端·javascript·vue.js·python·typescript·无人机
百思可瑞教育7 小时前
构建自己的Vue UI组件库:从设计到发布
前端·javascript·vue.js·ui·百思可瑞教育·北京百思教育
CappuccinoRose7 小时前
JavaScript 学习文档(二)
前端·javascript·学习·数据类型·运算符·箭头函数·变量声明
全栈前端老曹8 小时前
【MongoDB】深入研究副本集与高可用性——Replica Set 架构、故障转移、读写分离
前端·javascript·数据库·mongodb·架构·nosql·副本集
NCDS程序员8 小时前
v-model: /v-model/ :(v-bind)三者核心区别
前端·javascript·vue.js
小杨同学呀呀呀呀9 小时前
Ant Design Vue <a-timeline>时间轴组件失效解决方案
前端·javascript·vue.js·typescript·anti-design-vue
qq_532453539 小时前
使用 Three.js 构建沉浸式全景图AR
开发语言·javascript·ar