JavaScript的宏任務(wù)和微任務(wù)示例詳解
1. 前言
在 JavaScript 中,任務(wù)可以分為同步任務(wù)、異步任務(wù),而異步任務(wù)又進(jìn)一步細(xì)分為宏任務(wù)和微任務(wù)
- 同步任務(wù):指那些在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢后,才能開始執(zhí)行下一個任務(wù)
- 異步任務(wù):異步任務(wù)不會進(jìn)入主線程,而是進(jìn)入 “任務(wù)隊列” 中等待執(zhí)行
JavaScript 語言本身是單線程,這意味著在任何時刻,JavaScript 引擎中只有一條主線程來處理所有的任務(wù)
2. 同步任務(wù)
同步任務(wù)只有前一個任務(wù)執(zhí)行完畢后,才能開始執(zhí)行下一個任務(wù),特點是按照順序執(zhí)行,會阻塞后續(xù)代碼的執(zhí)行
console.log(1); console.log(2);
3. 異步任務(wù)
異步任務(wù)是只有當(dāng)主線程上的同步任務(wù)全部執(zhí)行完畢后,才會從任務(wù)隊列中取出異步任務(wù)進(jìn)入主線程執(zhí)行
console.log(1); // 同步任務(wù) setTimeout(() => console.log(3), 0) // 異步任務(wù),等待同步任務(wù)執(zhí)行完畢之后,才會執(zhí)行 console.log(2); // 同步任務(wù)
宏任務(wù)
宏任務(wù)是由宿主環(huán)境發(fā)起的異步任務(wù),通常包含較長的等待時間或需要等待 I/O 操作完成
常見的宏任務(wù):setTimeout / setInterval、ajax 請求、dom 事件
微任務(wù)
微任務(wù)通常用于處理 Promise 或其他需要盡快執(zhí)行的操作
常見的微任務(wù):Promise.then、async / await
setTimeout(() => console.log('定時器 - 宏任務(wù)'), 0)
Promise.resolve().then(value => {
console.log('Promise - 微任務(wù)');
})
console.log('123 - 同步任務(wù)')
// 123 - 同步任務(wù)
// Promise - 微任務(wù)
// 定時器 - 宏任務(wù)
4. 定時器的任務(wù)編排
在 JavaScript 中,setTimeout 函數(shù)的延時時間的最小值通常是 4 毫秒
這是因為在某些瀏覽器或操作系統(tǒng)中,為了優(yōu)化性能和節(jié)能,定時器的實際執(zhí)行可能會有一定的最小延遲
// 即使設(shè)置了非常小的延時,瀏覽器也可能不會立即執(zhí)行定時器回調(diào)函數(shù),而是等待至少 4 毫秒后才執(zhí)行
setTimeout(() => console.log('定時器'), 0)
設(shè)置定時器延時 200 毫秒執(zhí)行,那么它真的就會 200 毫秒后立即執(zhí)行嗎 ?通過以下代碼可以發(fā)現(xiàn)并不是這樣的
代碼解析原因:因為定時器是異步任務(wù),for 循環(huán)是同步任務(wù),所以定時器要等 for 循環(huán)執(zhí)行完之后才會執(zhí)行定時器
定時器的任務(wù)編排原理:到達(dá)延時時間會先將其放到宏任務(wù)隊列中,等待主線程處理完同步任務(wù),再來處理異步任務(wù)
console.log(new Date().getTime() / 1000);
setTimeout(() => {
console.log('定時器:' + new Date().getTime() / 1000);
}, 200)
console.log('同步任務(wù)')
for (let i = 0; i < 20000; i++) {
console.log('');
}
5. Promise 微任務(wù)處理邏輯
Promise 是微任務(wù)中的典型代表,Promise 的構(gòu)造函數(shù)代碼是立即執(zhí)行的,也就是說它是同步代碼
// 4. 定時器,放入宏任務(wù)隊列
setTimeout(() => console.log('定時器'))
new Promise(resolve => {
// 1. 這里是 promise 的構(gòu)造函數(shù),它是同步代碼
console.log('promise');
resolve()
}).then(() => {
// 3. 這里的代碼會放到微任務(wù)隊列
console.log('then');
})
// 2. 同步代碼
console.log('hello');
// 由于任務(wù)執(zhí)行順序是:同步任務(wù) > 微任務(wù) > 宏任務(wù),所以上面代碼的輸出順序為
// promise
// hello
// then
// 定時器
如果在定時器內(nèi)部再放入 Promise 和 定時器,那么執(zhí)行順序是怎么樣的 ?
setTimeout(() => {
// 1. 同步代碼
console.log('定時器')
// 因為它是在定時器內(nèi)部,所以它在宏任務(wù)執(zhí)行之后才會放入任務(wù)隊列,才會執(zhí)行
new Promise(resolve => {
// 2. 同步代碼
console.log('timeout promise');
resolve()
}).then(() => {
// 3. 微任務(wù)
console.log('timeout then');
})
// 4. 宏任務(wù)
setTimeout(() => console.log('定時器內(nèi)部的定時器'))
})
new Promise(resolve => {
console.log('promise');
resolve()
}).then(() => {
console.log('then');
})
console.log('hello');
// 其實很簡單,在上一段代碼的結(jié)果上,繼續(xù)按照任務(wù)執(zhí)行優(yōu)先順序執(zhí)行
// promise
// hello
// then
// 定時器
// timeout promise
// timeout then
// 定時器內(nèi)部的定時器
6. DOM 渲染任務(wù)
瀏覽器在解析頁面時,也是按照順序從上往下解析的,如下所示,先解析 utils.js,然后再渲染 h1 標(biāo)簽
也可以理解為先將解析 utils.js 的任務(wù)執(zhí)行完,再執(zhí)行渲染 h1 標(biāo)簽的任務(wù)
<head>
<script src="./utils.js"></script>
</head>
<body>
<h1>JavaScript 的宏任務(wù)和微任務(wù)</h1>
</body>
如果執(zhí)行 utils.js 的時間比較長,那么頁面將遲遲不渲染,進(jìn)入頁面會有較長時間的空白內(nèi)容
for (let index = 0; index < 5000000; index++) {
document.write(' ')
}
我們可以將 utils.js 的引入放到內(nèi)容渲染后面,改變?nèi)蝿?wù)的執(zhí)行順序,進(jìn)入頁面就不會有長時間的空白了
<body>
<h1>JavaScript 的宏任務(wù)和微任務(wù)</h1>
<script src="./utils.js"></script>
</body>
7. 任務(wù)共享內(nèi)存
任務(wù)共享內(nèi)存是指多個任務(wù)可以訪問同一塊內(nèi)存區(qū)域
多個定時器設(shè)置相同時間延時執(zhí)行,它們并不會同時執(zhí)行,而是到達(dá)設(shè)定的時間后將它們放入任務(wù)隊列,依次執(zhí)行任務(wù)
let i = 0
setTimeout(() => {
// 宏任務(wù)
console.log(++i);
}, 1000)
setTimeout(() => {
// 宏任務(wù)
console.log(++i);
}, 1000)
8. 進(jìn)度條實例體驗任務(wù)輪詢
先寫好一個進(jìn)度條的樣式
<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>
定時器其實就是往任務(wù)隊列中不斷的放入任務(wù),到達(dá)執(zhí)行時間后,交由主線程依次執(zhí)行任務(wù)
function handle() {
let i = 0;
(function run() {
if (++i <= 100) {
progress.innerHTML = i
progress.style.width = i + '%'
setTimeout(run, 50)
}
})();
}
handle()
9. 任務(wù)拆分為多個子任務(wù)
現(xiàn)有以下代碼,前面的累加函數(shù)需要較長時間才能執(zhí)行完成,阻塞了后面代碼的運行,我們來看一下怎么解決這個問題
let count = 0
let num = 987654321
function task() {
for (let i = 0; i < num; i++) {
count += num--
}
console.log(count);
}
task() // 同步任務(wù)
console.log('hello'); // 同步任務(wù),需要等待 task() 執(zhí)行完后才能執(zhí)行
思路是將大量的計算放入異步任務(wù),讓同步任務(wù)先執(zhí)行,以免造成阻塞
let count = 0
let num = 10000000
function task() {
// 首次調(diào)用先累加一次
for (let i = 0; i <= num; i++) {
if (num <= 0) break;
count += num--
}
if (num > 0) {
// 后續(xù)累加,放入宏任務(wù)執(zhí)行
setTimeout(task)
} else {
console.log(count)
}
}
task()
console.log('hello')
10. Promise 微任務(wù)處理復(fù)雜邏輯
將定時器放入 Promise 也可以處理復(fù)雜邏輯,不影響同步任務(wù)的執(zhí)行,但下面用的還是宏任務(wù)(定時器)
let count = 0
let num = 987654321
// 由于 promise 里面的代碼是同步執(zhí)行的,在里面直接寫循環(huán)還是會卡住,所以可以寫個定時器
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 回調(diào)方法是微任務(wù),那么我們可以將代碼調(diào)整為:
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');
總結(jié)
到此這篇關(guān)于JavaScript宏任務(wù)和微任務(wù)示例詳解的文章就介紹到這了,更多相關(guān)JS宏任務(wù)和微任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實現(xiàn)數(shù)值自動增加動畫
這篇文章主要為大家詳細(xì)介紹了JavaScript實現(xiàn)數(shù)值自動增加動畫,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
layer設(shè)置maxWidth及maxHeight解決方案
這篇文章主要介紹了layer設(shè)置maxWidth及maxHeight解決方案,非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下2019-07-07
JS實現(xiàn)的Unicode編碼轉(zhuǎn)換操作示例
這篇文章主要介紹了JS實現(xiàn)的Unicode編碼轉(zhuǎn)換操作,結(jié)合完整實例形式分析了javascript實現(xiàn)Unicode編碼轉(zhuǎn)換的具體操作技巧,需要的朋友可以參考下2017-04-04
javascript拖曳互換div的位置實現(xiàn)示例
一個div拖動互換位置的demo,還有很大優(yōu)化的空間,利用dom元素的dragstart/ondragover/ondrop事件完成,感興趣的可以了解一下2021-06-06

