web前端知識(shí):js中的微任務(wù)和宏任務(wù)
js中什么是微任務(wù)和宏任務(wù)
在 JavaScript 引擎中,任務(wù)分為兩種類(lèi)型:微任務(wù)(microtask)和宏任務(wù)(macrotask)。 微任務(wù)是指在當(dāng)前任務(wù)執(zhí)行結(jié)束后立即執(zhí)行的任務(wù),它可以看作是在當(dāng)前任務(wù)的“尾巴”添加的任務(wù)。常見(jiàn)的微任務(wù)包括 Promise 回調(diào)和?process.nextTick。 宏任務(wù)是指需要排隊(duì)等待 JavaScript 引擎空閑時(shí)才能執(zhí)行的任務(wù)。常見(jiàn)的宏任務(wù)包括 setTimeout、setInterval、I/O 操作、DOM 事件等。 JavaScript 引擎會(huì)先執(zhí)行當(dāng)前任務(wù)中的所有微任務(wù),然后再執(zhí)行宏任務(wù)隊(duì)列中的第一個(gè)任務(wù)。這個(gè)過(guò)程會(huì)不斷重復(fù),直到宏任務(wù)隊(duì)列中的任務(wù)被全部執(zhí)行完畢。
JS為什么要區(qū)分微任務(wù)和宏任務(wù)
JavaScript 之所以要區(qū)分微任務(wù)和宏任務(wù),是因?yàn)槲⑷蝿?wù)和宏任務(wù)的執(zhí)行順序不同,這對(duì) Web 開(kāi)發(fā)中一些異步操作的實(shí)現(xiàn)有著重要的影響。 在 JavaScript 中,微任務(wù)會(huì)優(yōu)先于宏任務(wù)執(zhí)行。這意味著在當(dāng)前任務(wù)執(zhí)行結(jié)束后,所有微任務(wù)都會(huì)被立即執(zhí)行,而宏任務(wù)只有在所有微任務(wù)執(zhí)行完畢后才會(huì)執(zhí)行。這種執(zhí)行順序保證了微任務(wù)的優(yōu)先級(jí),可以避免一些問(wèn)題的出現(xiàn),比如處理 Promise 對(duì)象時(shí)可能會(huì)出現(xiàn)的競(jìng)態(tài)條件。 舉個(gè)例子,當(dāng)我們使用 Promise 對(duì)象時(shí),它會(huì)返回一個(gè) Promise 實(shí)例并將回調(diào)函數(shù)放入微任務(wù)隊(duì)列中。當(dāng) Promise 的狀態(tài)發(fā)生改變時(shí),它會(huì)立即執(zhí)行微任務(wù)隊(duì)列中的回調(diào)函數(shù),而不是等待當(dāng)前任務(wù)結(jié)束后再執(zhí)行。這種特性可以保證 Promise 回調(diào)函數(shù)的執(zhí)行順序,避免出現(xiàn)競(jìng)態(tài)條件,從而使代碼更加可靠。 另一方面,宏任務(wù)的執(zhí)行是在當(dāng)前任務(wù)結(jié)束后才會(huì)執(zhí)行的,這意味著可以將一些耗時(shí)的操作放入宏任務(wù)隊(duì)列中,從而避免阻塞當(dāng)前任務(wù)的執(zhí)行。比如,我們可以將一些需要等待一段時(shí)間才能執(zhí)行的代碼放入 setTimeout 的回調(diào)函數(shù)中,這樣可以使頁(yè)面在執(zhí)行這些代碼的同時(shí)仍然保持響應(yīng),提高用戶(hù)體驗(yàn)。 因此,JavaScript 之所以要區(qū)分微任務(wù)和宏任務(wù),是為了保證異步操作的正確性和性能。
JS中微任務(wù)和宏任務(wù)執(zhí)行順序
首先執(zhí)行當(dāng)前代碼(同步任務(wù)),直到遇到第一個(gè)宏任務(wù)或微任務(wù)。
如果遇到微任務(wù),則將它添加到微任務(wù)隊(duì)列中,繼續(xù)執(zhí)行同步任務(wù)。
如果遇到宏任務(wù),則將它添加到宏任務(wù)隊(duì)列中,繼續(xù)執(zhí)行同步任務(wù)。
當(dāng)前任務(wù)執(zhí)行完畢后,JavaScript 引擎會(huì)先執(zhí)行所有微任務(wù)隊(duì)列中的任務(wù),直到微任務(wù)隊(duì)列為空。
然后執(zhí)行宏任務(wù)隊(duì)列中的第一個(gè)任務(wù),直到宏任務(wù)隊(duì)列為空。
重復(fù)步驟 4 和步驟 5,直到所有任務(wù)都被執(zhí)行完畢。 需要注意的是,微任務(wù)比宏任務(wù)優(yōu)先級(jí)要高,因此在同一個(gè)任務(wù)中,如果既有微任務(wù)又有宏任務(wù),那么微任務(wù)會(huì)先執(zhí)行完畢。而在不同的任務(wù)中,宏任務(wù)的執(zhí)行優(yōu)先級(jí)要高于微任務(wù),因此在一個(gè)宏任務(wù)執(zhí)行完畢后,它才會(huì)執(zhí)行下一個(gè)宏任務(wù)和微任務(wù)隊(duì)列中的任務(wù)。 舉個(gè)例子,假設(shè)當(dāng)前代碼中有一個(gè) setTimeout 和一個(gè) Promise,它們分別對(duì)應(yīng)一個(gè)宏任務(wù)和一個(gè)微任務(wù)。那么執(zhí)行順序如下:
1). 執(zhí)行當(dāng)前代碼,將 setTimeout 和 Promise 添加到宏任務(wù)和微任務(wù)隊(duì)列中。
2). 當(dāng)前任務(wù)執(zhí)行完畢,JavaScript 引擎先執(zhí)行微任務(wù)隊(duì)列中的 Promise 回調(diào)函數(shù)。
3). 微任務(wù)隊(duì)列為空后,再執(zhí)行宏任務(wù)隊(duì)列中的 setTimeout 回調(diào)函數(shù)。 需要注意的是,在一些特殊情況下,微任務(wù)和宏任務(wù)的執(zhí)行順序可能會(huì)發(fā)生變化,比如在使用 MutationObserver 監(jiān)聽(tīng) DOM 變化時(shí),它會(huì)被視為一個(gè)微任務(wù),但是它的執(zhí)行順序可能會(huì)比其他微任務(wù)更靠后。因此,需要根據(jù)具體情況來(lái)理解和處理微任務(wù)和宏任務(wù)的執(zhí)行順序。
js微任務(wù)和宏任務(wù)有哪些
微任務(wù):Promise 回調(diào)函數(shù)、process.nextTick、Object.observe(已廢棄)、MutationObserver。
宏任務(wù):setTimeout、setInterval、setImmediate(Node.js 獨(dú)有)、requestAnimationFrame、I/O 操作、UI 渲染。
需要注意的是,不同的 JavaScript 引擎可能會(huì)存在一些差異,有些任務(wù)可能既可以作為微任務(wù),也可以作為宏任務(wù),比如在一些瀏覽器中,使用 MutationObserver 監(jiān)聽(tīng) DOM 變化時(shí),它會(huì)被視為一個(gè)微任務(wù),但是在一些 Node.js 版本中,它會(huì)被視為一個(gè)宏任務(wù)。 另外,需要注意的是,Promise 回調(diào)函數(shù)是微任務(wù),但是它的內(nèi)部代碼可能會(huì)包含其他的異步操作,這些異步操作可能是微任務(wù)或宏任務(wù),因此在處理 Promise 時(shí)需要考慮到它內(nèi)部可能包含的其他異步操作。
案例
1.微任務(wù):Promise 回調(diào)函數(shù),宏任務(wù):setTimeout 回調(diào)函數(shù)
console.log('start');setTimeout(()?=>?console.log('setTimeout'),?0);Promise.resolve().then(()?=>?console.log('Promise'));console.log('end');//?start//?end//?Promise//?setTimeout
解析: 首先輸出?start,然后通過(guò) setTimeout 方法注冊(cè)了一個(gè)回調(diào)函數(shù),它會(huì)被添加到宏任務(wù)隊(duì)列中。接著創(chuàng)建了一個(gè) Promise 實(shí)例,并且通過(guò) then 方法注冊(cè)了一個(gè)回調(diào)函數(shù),在 Promise 對(duì)象的狀態(tài)改變時(shí)會(huì)執(zhí)行這個(gè)回調(diào)函數(shù)。接著輸出?end。因?yàn)?Promise 回調(diào)函數(shù)是微任務(wù),所以它會(huì)被添加到微任務(wù)隊(duì)列中,等待執(zhí)行。等到主線程的同步任務(wù)執(zhí)行完畢后,JavaScript 引擎會(huì)先執(zhí)行微任務(wù)隊(duì)列中的任務(wù),輸出?Promise,然后執(zhí)行宏任務(wù)隊(duì)列中的任務(wù),輸出?setTimeout。
2.微任務(wù):process.nextTick 回調(diào)函數(shù),宏任務(wù):setImmediate 回調(diào)函數(shù)
console.log('start');setImmediate(()?=>?console.log('setImmediate'));process.nextTick(()?=>?console.log('process.nextTick'));console.log('end');//?start//?end//?process.nextTick//?setImmediate
解析: 在 Node.js 環(huán)境中,process.nextTick 回調(diào)函數(shù)是排在微任務(wù)隊(duì)列最前面的,優(yōu)先級(jí)比 Promise 回調(diào)函數(shù)還要高。而 setImmediate 回調(diào)函數(shù)是排在宏任務(wù)隊(duì)列最后面的。所以,以上代碼會(huì)先輸出?start,然后輸出?end。等到主線程的同步任務(wù)執(zhí)行完畢后,JavaScript 引擎會(huì)先執(zhí)行微任務(wù)隊(duì)列中的任務(wù),輸出?process.nextTick,然后執(zhí)行宏任務(wù)隊(duì)列中的任務(wù),輸出?setImmediate。
3.微任務(wù):Promise 回調(diào)函數(shù),宏任務(wù):requestAnimationFrame 回調(diào)函數(shù)
console.log('start');requestAnimationFrame(()?=>?console.log('requestAnimationFrame'));Promise.resolve().then(()?=>?console.log('Promise'));console.log('end');//?start//?end//?Promise//?requestAnimationFrame
解析: 首先輸出?start,然后通過(guò) requestAnimationFrame 方法注冊(cè)了一個(gè)回調(diào)函數(shù),它會(huì)被添加到宏任務(wù)隊(duì)列中。接著創(chuàng)建了一個(gè) Promise 實(shí)例,并且通過(guò) then 方法注冊(cè)了一個(gè)回調(diào)函數(shù),在 Promise 對(duì)象的狀態(tài)改變時(shí)會(huì)執(zhí)行這個(gè)回調(diào)函數(shù)。接著輸出?end。因?yàn)?Promise 回調(diào)函數(shù)是微任務(wù),所以它會(huì)被添加到微任務(wù)隊(duì)列中,等待執(zhí)行。等到主線程的同步任務(wù)執(zhí)行完畢后,JavaScript 引擎會(huì)先執(zhí)行微任務(wù)隊(duì)列中的任務(wù),輸出?Promise,然后執(zhí)行宏任務(wù)隊(duì)列中的任務(wù),輸出?requestAnimationFrame。
4.微任務(wù):Promise 回調(diào)函數(shù),宏任務(wù):XMLHttpRequest 回調(diào)函數(shù)
console.log('start');const?xhr?=?new?XMLHttpRequest();xhr.open('GET',?'https://jsonplaceholder.typicode.com/users/1');xhr.onload?=?()?=>?console.log('XMLHttpRequest');xhr.send();Promise.resolve().then(()?=>?console.log('Promise'));console.log('end');//?start//?end//?Promise//?XMLHttpRequest
解析: 首先輸出?start,然后創(chuàng)建了一個(gè) XMLHttpRequest 對(duì)象,并且通過(guò) open 方法和 send 方法發(fā)送了一個(gè) GET 請(qǐng)求。接著通過(guò) onload 方法注冊(cè)了一個(gè)回調(diào)函數(shù),在請(qǐng)求成功后會(huì)執(zhí)行這個(gè)回調(diào)函數(shù)。接著創(chuàng)建了一個(gè) Promise 實(shí)例,并且通過(guò) then 方法注冊(cè)了一個(gè)回調(diào)函數(shù),在 Promise 對(duì)象的狀態(tài)改變時(shí)會(huì)執(zhí)行這個(gè)回調(diào)函數(shù)。接著輸出?end。因?yàn)?Promise 回調(diào)函數(shù)是微任務(wù),所以它會(huì)被添加到微任務(wù)隊(duì)列中,等待執(zhí)行。等到主線程的同步任務(wù)執(zhí)行完畢后,JavaScript 引擎會(huì)先執(zhí)行微任務(wù)隊(duì)列中的任務(wù),輸出?Promise,然后等待 XMLHttpRequest 對(duì)象的回調(diào)函數(shù)執(zhí)行。當(dāng)請(qǐng)求成功后,JavaScript 引擎會(huì)執(zhí)行宏任務(wù)隊(duì)列中的任務(wù),輸出?XMLHttpRequest。
結(jié)語(yǔ)
牽手 持續(xù)為你分享各類(lèi)知識(shí)和軟件 ,歡迎訪問(wèn)、關(guān)注、討論 并留下你的小心心?