JS?中的?Event?Loop?是什么你真的懂
Event Loop,簡(jiǎn)單翻譯就是 事件循環(huán),是 JS 語(yǔ)言下實(shí)現(xiàn)運(yùn)行時(shí)的一個(gè)機(jī)制。
JS 的異步并不像其他語(yǔ)言(比如 Java)的異步那樣可以實(shí)現(xiàn)真正的并發(fā)執(zhí)行,本身其實(shí)是個(gè)單線程。
JS 是維護(hù)了一個(gè) 任務(wù)隊(duì)列,每當(dāng)要執(zhí)行一些異步任務(wù),比如定時(shí)器或者是點(diǎn)擊按鈕觸發(fā)的事件響應(yīng)函數(shù)。它們不會(huì)立即執(zhí)行,而是放到這個(gè)隊(duì)列里,等待已經(jīng)在排隊(duì)的其他任務(wù)先執(zhí)行完,才輪到它們。
隊(duì)列是一個(gè)操作受限的有序列表,表現(xiàn)為為先進(jìn)入的元素必須先出去,即 “先進(jìn)先出”,很像排隊(duì)的感覺。
不過也有一些特殊的隊(duì)列,比如優(yōu)先級(jí)隊(duì)列,它是優(yōu)先級(jí)高的元素先出隊(duì)。
之所以叫 Event Loop,因?yàn)樗倪壿嬁梢悦枋鰹橄旅娲a:?
while (queue.waitForMessage()) {
queue.processNextMessage();
}當(dāng)一個(gè)任務(wù)被完成后,隊(duì)列會(huì)變成等待下一個(gè)任務(wù)狀態(tài),然后處理下一個(gè)任務(wù),如此循環(huán)往復(fù)。
因?yàn)?JS 的代碼執(zhí)行本身是一個(gè)單線程,為了不讓執(zhí)行阻塞,JS 會(huì)把網(wǎng)絡(luò)請(qǐng)求操作、渲染瀏覽器頁(yè)面等操作,交給其他的線程,等待其他線程處理好把結(jié)果返回給 JS。
所以 JS 不合適 CPU 密集型,更適合 IO 密集型的場(chǎng)景。因?yàn)樗挥幸粋€(gè)線程,如果計(jì)算耗時(shí)太長(zhǎng),就會(huì)阻塞其他要執(zhí)行的任務(wù),導(dǎo)致卡頓,甚至崩潰。
setTimeout 定時(shí)器并不準(zhǔn)
setTimeout 在特定時(shí)間后要執(zhí)行的函數(shù),并不會(huì)立即執(zhí)行,而是會(huì)先放到任務(wù)隊(duì)列中,先等待前面的任務(wù)同步執(zhí)行完成了,才能執(zhí)行我們這個(gè)。
下面看一個(gè)例子,因?yàn)榈谝粋€(gè) setTimout 有一個(gè)非常耗時(shí)的同步任務(wù),導(dǎo)致下一個(gè) setTimeout 的執(zhí)行阻塞,比前面一個(gè) setTimeout 執(zhí)行要慢半秒。
const start = new Date().getTime();
setTimeout(() => {
console.log('1:', new Date().getTime() - start);
let num = 0;
for (let i = 0; i < 999999999; i++) {
num = i;
}
}, 1000);
setTimeout(() => {
console.log('2:', new Date().getTime() - start);
}, 1000);
/**
* 輸出結(jié)果:
* 1: 1001
* 2: 1505
*/定時(shí)器的時(shí)間,指的是能執(zhí)行的最早時(shí)間,但不能保證一定能在這個(gè)時(shí)間點(diǎn)立即執(zhí)行。
宏任務(wù)和微任務(wù)
任務(wù)隊(duì)列并不是嚴(yán)格意義上的先進(jìn)先出的正常隊(duì)列,是可以調(diào)整執(zhí)行順序的。
我們將要執(zhí)行的任務(wù)分為宏任務(wù)和微任務(wù),其中宏任務(wù)是正常的先進(jìn)先出,而微任務(wù)則是可以插隊(duì),優(yōu)先于宏任務(wù)先執(zhí)行。宏任務(wù)必須在所有微任務(wù)執(zhí)行后才能執(zhí)行。
當(dāng)我們給任務(wù)隊(duì)列添加一個(gè)微任務(wù)時(shí),它會(huì)跑到任務(wù)隊(duì)列宏任務(wù)前。多個(gè)微任務(wù)入隊(duì)時(shí),會(huì)保持它們的相對(duì)順序。
宏任務(wù)有:
- script,即 HTML 嵌入的腳本。
- setTimeout / setInterval 定時(shí)器。
- setImmediate,這是 nodejs 特有的 API。
- requestAnimationFrame,會(huì)在頁(yè)面重繪前執(zhí)行。
- I/O 操作,比如網(wǎng)絡(luò)請(qǐng)求完成的回調(diào)函數(shù)執(zhí)行任務(wù)、還比如點(diǎn)擊按鈕要執(zhí)行的回調(diào)等。這些操作其實(shí)是其他的線程完成后觸發(fā)的,暫且歸納為 I/O 操作。
微任務(wù)有:
- Promise 從 pending 狀態(tài)轉(zhuǎn)換為其他狀態(tài)時(shí),觸發(fā) then/catch/finaly 中的函數(shù),比如 Promise.resolve().then(fn) 。這是最常見的微任務(wù)。
- MutationObserver,用于監(jiān)聽 DOM 的變化。
- process.nextTick,nodejs 特有的 API。
任務(wù)隊(duì)列,理論上一個(gè)就夠了,但也可以是多個(gè)隊(duì)列的組合,沒有強(qiáng)行要求。
多個(gè)任務(wù)隊(duì)列的實(shí)現(xiàn)可以更好地實(shí)現(xiàn)優(yōu)先級(jí)的控制,比如對(duì)于定時(shí)器任務(wù),理論上應(yīng)該是在多個(gè)宏任務(wù)中最先執(zhí)行比較好。瀏覽器沒考慮這種情況,但 nodejs 給宏任務(wù)中也設(shè)置優(yōu)先級(jí),會(huì)讓定時(shí)器任務(wù)最先執(zhí)行。
Event Loop 還是挺復(fù)雜的,標(biāo)準(zhǔn)文檔也比較長(zhǎng),我也沒怎么看,感興趣可以看看。
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops。
一道經(jīng)典異步題
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log('script end')解題思路為:
- 找到同步代碼。同步代碼有:普通同步代碼、new Promise(fn) 執(zhí)行傳入的回調(diào)函數(shù)、async 執(zhí)行時(shí)遇到 await 的前面部分(包括 await 的右側(cè)函數(shù)執(zhí)行也是同步的,這里是易錯(cuò)點(diǎn) )。
- 看看任務(wù)隊(duì)列中有哪些微任務(wù)和宏任務(wù),記住微任務(wù)全執(zhí)行完了才會(huì)執(zhí)行宏任務(wù)。
- 執(zhí)行任務(wù),任務(wù)里面的異步任務(wù)又按順序進(jìn)入到任務(wù)隊(duì)列。
結(jié)果是:
// 同步代碼
script start
async1 start
async2
promise1
script end
// 微任務(wù)
async1 end
promise2
// 宏任務(wù)
setTimeout
結(jié)尾
JS 運(yùn)行機(jī)制是單線程,當(dāng)有多個(gè)異步任務(wù)要同時(shí)執(zhí)行,并不能并發(fā)執(zhí)行,必須讓優(yōu)先級(jí)高的任務(wù)執(zhí)行完才能執(zhí)行后面的。如果正在執(zhí)行的任務(wù)比較耗時(shí),會(huì)導(dǎo)致后面的任務(wù)被阻塞。
Event Loop 的機(jī)制中,最基本的一條就是:微任務(wù)比宏任務(wù)先執(zhí)行。
到此這篇關(guān)于JS 中的 Event Loop 是什么?的文章就介紹到這了,更多相關(guān)JS 中的 Event Loop內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS回調(diào)函數(shù)原理與用法詳解【附PHP回調(diào)函數(shù)】
這篇文章主要介紹了JS回調(diào)函數(shù)原理與用法,結(jié)合實(shí)例形式詳細(xì)分析了JavaScript回調(diào)函數(shù)的概念、原理、用法,并給出了PHP回調(diào)函數(shù)的使用示例,需要的朋友可以參考下2019-07-07
javascript使用正則表達(dá)式實(shí)現(xiàn)去掉空格之后的字符
這篇文章主要介紹了javascript使用正則表達(dá)式實(shí)現(xiàn)去掉空格之后的字符的方法,需要的朋友可以參考下2015-02-02
使用遞歸遍歷對(duì)象獲得value值的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄褂眠f歸遍歷對(duì)象獲得value值的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06
JavaScript跨瀏覽器獲取頁(yè)面中相同class節(jié)點(diǎn)的方法
這篇文章主要介紹了JavaScript跨瀏覽器獲取頁(yè)面中相同class節(jié)點(diǎn)的方法,本文講解使用getELementsByClassName函數(shù)解決這個(gè)需求,并給了一個(gè)開源的getELementsByClassName函數(shù)實(shí)現(xiàn),功能更加強(qiáng)大,需要的朋友可以參考下2015-03-03
uniapp使用第三方UI庫(kù)uview-plus的方法
uview-plus是uni-app全面兼容nvue的uni-app生態(tài)框架,全面的組件和便捷的工具會(huì)讓您信手拈來,下面這篇文章主要給大家介紹了關(guān)于uniapp使用第三方UI庫(kù)uview-plus的方法,需要的朋友可以參考下2023-04-04
基于JS實(shí)現(xiàn)一個(gè)隨機(jī)生成驗(yàn)證碼功能
這篇文章主要介紹了基于JS實(shí)現(xiàn)一個(gè)隨機(jī)生成驗(yàn)證碼功能,隨機(jī)生成一個(gè)四位數(shù)的驗(yàn)證碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
DOM節(jié)點(diǎn)深度克隆函數(shù)cloneNode()用法實(shí)例
這篇文章主要介紹了DOM節(jié)點(diǎn)深度克隆函數(shù)cloneNode()用法,實(shí)例分析了cloneNode()函數(shù)深度復(fù)制的操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01
js利用iframe實(shí)現(xiàn)選項(xiàng)卡效果
這篇文章主要為大家詳細(xì)介紹了js利用iframe實(shí)現(xiàn)選項(xiàng)卡效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
javascript中createElement的兩種創(chuàng)建方式
這篇文章主要介紹了javascript中createElement的兩種創(chuàng)建方式,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-05-05

