詳解JS事件循環(huán)及宏任務(wù)微任務(wù)的原理
本質(zhì)上來說,JavaScript是同步的、阻塞的、單線程語言,不管是在瀏覽器中還是nodejs環(huán)境下。瀏覽器在執(zhí)行js代碼和渲染DOM節(jié)點(diǎn)都是在同一個(gè)線程中,執(zhí)行js代碼就無法渲染DOM,渲染DOM的時(shí)候就無法執(zhí)行js代碼。如果按照這種同步方式執(zhí)行,頁面的渲染將會(huì)出現(xiàn)白屏甚至是報(bào)錯(cuò),特別是遇到一些耗時(shí)比較長的網(wǎng)絡(luò)請(qǐng)求或者js代碼,因此在實(shí)際開發(fā)中一般是通過異步的方式解決。
什么是異步?js是一步一步執(zhí)行代碼的,遇到alert
這種阻塞代碼時(shí),js將會(huì)停止往下執(zhí)行直到阻塞代碼執(zhí)行完畢。異步就是將函數(shù)放在單獨(dú)的異步隊(duì)列中,不會(huì)產(chǎn)生阻塞,js可以繼續(xù)往下執(zhí)行,等到同步代碼執(zhí)行完畢后再執(zhí)行異步隊(duì)列中的函數(shù)。因此,js會(huì)先執(zhí)行完同步代碼,才會(huì)執(zhí)行異步代碼。
異步也有細(xì)分,每一個(gè)異步函數(shù)可當(dāng)作是一個(gè)待執(zhí)行的任務(wù),可分為宏任務(wù)和微任務(wù)。本文重點(diǎn)介紹瀏覽器的事件循環(huán),nodejs環(huán)境下的事件循環(huán)放在下一篇中繼續(xù)探討。
宏任務(wù)
宏任務(wù),也可簡(jiǎn)單的說成是任務(wù),在下一輪DOM渲染之后執(zhí)行。常見的宏任務(wù)有:
setTimeout:設(shè)置一個(gè)定時(shí)器,該定時(shí)器會(huì)在設(shè)置的延遲時(shí)間到期后執(zhí)行一個(gè)函數(shù)或者指定的代碼塊。值得注意的是,setTimeout
不一定會(huì)在延遲時(shí)間到達(dá)后就立即執(zhí)行函數(shù),而是會(huì)判斷執(zhí)行隊(duì)列中是否還有函數(shù)沒有處理,如果沒有了并且棧為空,setTimeout
才會(huì)在延遲時(shí)間到達(dá)后執(zhí)行函數(shù)。
// setTimeout 延遲執(zhí)行不等于到期時(shí)立即執(zhí)行 let now = new Date().getSeconds(); setTimeout(() => { console.log('this is setTimeout 0'); }, 0); setTimeout(() => { console.log('this is setTimeout 200'); }, 200); while(true) { if (new Date().getSeconds() - now >= 2) { console.log('break out while loop'); break; } }
運(yùn)行結(jié)果
break out while loop
this is setTimeout 0
this is setTimeout 200
先執(zhí)行同步代碼,再執(zhí)行異步。setTimeout(() => {}, 0)
表示0毫秒后立即執(zhí)行函數(shù),但是當(dāng)前執(zhí)行隊(duì)列中還有未處理完的while循環(huán),因此需要等到while循環(huán)執(zhí)行完畢后,才會(huì)根據(jù)延遲到期時(shí)間執(zhí)行函數(shù)。
setInterval:設(shè)置定時(shí)器,表示在固定的時(shí)間間隔內(nèi),重復(fù)執(zhí)行某一函數(shù)或者特定的代碼塊。注意使用setInterval
有最小延遲時(shí)間限制以及確保執(zhí)行時(shí)間要小于間隔時(shí)間,如果執(zhí)行時(shí)間無法確定,則應(yīng)采用遞歸調(diào)用setTimeout
的方式代替。
網(wǎng)絡(luò)請(qǐng)求:只要是指XMLHttpRequest等網(wǎng)絡(luò)請(qǐng)求
微任務(wù)
微任務(wù),在下一輪DOM渲染之前執(zhí)行,微任務(wù)比宏任務(wù)更早執(zhí)。常見的微任務(wù)有:
promise:表示一個(gè)異步操作最終的結(jié)果和返回值,可能會(huì)失敗,也可能成功。異步函數(shù)在執(zhí)行時(shí),什么時(shí)候返回結(jié)果是不可預(yù)料的,Promise
把異步操作的返回值和函數(shù)關(guān)聯(lián)起來,保證在異步執(zhí)行結(jié)束后會(huì)執(zhí)行對(duì)應(yīng)的函數(shù),并通過函數(shù)返回操作值。這種效果就類似于把異步代碼“同步執(zhí)行”。
queueMicrotask:將函數(shù)添加到微任務(wù)隊(duì)
console.log('start'); // 微任務(wù)隊(duì)列 Promise.resolve().then(() => { console.log('promise then'); }); queueMicrotask(() => { console.log('queueMicrotask'); }); console.log('end');
運(yùn)行結(jié)果
start
end
promise then
queueMicrotask
事件循環(huán)
因?yàn)橛挟惒讲僮鞯拇嬖冢猿霈F(xiàn)了事件循環(huán),如果都是同步操作,一行一行執(zhí)行代碼,事件循環(huán)也就失去了用武之地。在了解事件循環(huán)前,還需要補(bǔ)充js的執(zhí)行過程:
js在執(zhí)行代碼時(shí),遇到函數(shù)就會(huì)將其添加到調(diào)用棧中,每一幀都會(huì)存儲(chǔ)當(dāng)前函數(shù)的參數(shù)和局部變量,當(dāng)一個(gè)函數(shù)執(zhí)行完畢,則會(huì)從調(diào)用棧中彈出,直到棧被清空,那么程序也就執(zhí)行完畢。在執(zhí)行的過程中,需要的引用數(shù)據(jù)都是從堆中獲取。
在實(shí)際開發(fā)中,往往是同步代碼和異步代碼都有。在js執(zhí)行時(shí),還是從第一行代碼開始執(zhí)行,遇到函數(shù)就將其添加到棧中,然后執(zhí)行同步操作;如果遇到異步函數(shù),則根據(jù)其類型,宏任務(wù)就添加到宏任務(wù)隊(duì)列,微任務(wù)添加到微任務(wù)隊(duì)列。直到同步代碼執(zhí)行完畢,則開始執(zhí)行異步操作。
異步操作后于同步操作,異步操作內(nèi)部也是分先后順序的??偟膩碚f:
- 微任務(wù)先于宏任務(wù)執(zhí)行
- 微任務(wù)與微任務(wù)之間根據(jù)先后順序執(zhí)行,宏任務(wù)與宏任務(wù)之間根據(jù)延遲時(shí)間順序執(zhí)行
- 微任務(wù)在下一輪DOM渲染前執(zhí)行,宏任務(wù)在下一輪DOM渲染之后執(zhí)行
- 每個(gè)任務(wù)的執(zhí)行都是一次出棧操作,直到棧被清空
微任務(wù)比宏任務(wù)先執(zhí)行
console.log('start'); // 宏任務(wù)隊(duì)列 setTimeout(() => { console.log('setTimeout'); }); // 微任務(wù)隊(duì)列 Promise.resolve().then(() => { console.log('promise then'); }); console.log('end');
執(zhí)行結(jié)果
start
end
promise then
setTimeout
微任務(wù)在下一輪DOM渲染前執(zhí)行,宏任務(wù)在之后執(zhí)行
let div = document.createElement('div'); div.innerHTML = 'hello world'; document.body.appendChild(div); let divList = document.getElementByTagName('div'); console.log('同步任務(wù) length ---', list.length); console.log('start'); setTimeout(() => { console.log('setTimeout length ---', list.length); alert('宏任務(wù) setTimeout 阻塞'); // 使用alert阻塞js執(zhí)行 }); Promise.resolve().then(() => { console.log('promise then length ---', list.length); alert('微任務(wù) promise then 阻塞); }); console.log('end');
事件循環(huán)
event loop
會(huì)持續(xù)監(jiān)聽是否有異步操作,如果有則添加到對(duì)應(yīng)的隊(duì)列中,等待執(zhí)行。例如在宏任務(wù)中添加微任務(wù),或者在微任務(wù)中添加宏任務(wù),當(dāng)前任務(wù)執(zhí)行完后,可能還會(huì)有新的任務(wù)添加到事件循環(huán)中。
宏任務(wù)與微任務(wù)
微任務(wù)中創(chuàng)建宏任務(wù)
new Promise((resolve) => { console.log('promise 1'); setTimeout(() => { console.log('setTimeout 1'); }, 500); resolve(); }).then(() => { console.log('promise then'); setTimeout(() => { console.log('setTimeout 2'); }, 0); }); new Promise((resolve) => { console.log('promise 2'); resolve(); })
運(yùn)行結(jié)果
promise 1
promise 2
promise then
setTimeout 2
setTimeout 1
解析
js執(zhí)行代碼,遇到兩個(gè)Promise,則分別添加到微任務(wù)隊(duì)列,同步代碼執(zhí)行完畢。
在微任務(wù)隊(duì)列中根據(jù)先進(jìn)先出,第一個(gè)Promise先執(zhí)行,遇到setTimeout,則添加到宏任務(wù)隊(duì)列,resolve()
返回執(zhí)行結(jié)果并執(zhí)行then,事件循環(huán)將其繼續(xù)添加到微任務(wù)隊(duì)列;第一個(gè)Promise執(zhí)行完畢,執(zhí)行第二個(gè)Promise。
繼續(xù)執(zhí)行微任務(wù)隊(duì)列,直到清空隊(duì)列。遇到setTimeout,并將其添加到宏任務(wù)隊(duì)列
宏任務(wù)隊(duì)列現(xiàn)在有兩個(gè)任務(wù)待執(zhí)行,由于第二個(gè)setTimeout的延遲事件更小,則優(yōu)先執(zhí)行第二個(gè);如果相等,則按照順序執(zhí)行。
繼續(xù)執(zhí)行宏任務(wù)隊(duì)列,直到清空隊(duì)列。
宏任務(wù)中創(chuàng)建微任務(wù)
setTimeout(() => { console.log('setTimeout 1'); new Promise((resolve) => { console.log('promise 1'); resolve(); }).then(() => { console.log('promise then'); }) }, 500); setTimeout(() => { console.log('setTimeout 2'); new Promise((resolve) => { console.log('promise 2'); resolve(); }) }, 0);
運(yùn)行結(jié)果
setTimeout 2
promise 2
setTimeout 1
promise 1
promise then
解析
js執(zhí)行代碼,遇到兩個(gè)setTimeout,將其添加到宏任務(wù)隊(duì)列,同步代碼執(zhí)行完畢
先檢查微任務(wù)隊(duì)列中是否有待處理的,剛開始肯定沒有,因此直接執(zhí)行宏任務(wù)隊(duì)列中的任務(wù)。第二個(gè)為零延遲,需要優(yōu)先執(zhí)行。遇到Promise,將其添加到微任務(wù)隊(duì)列。第一個(gè)宏任務(wù)執(zhí)行完畢
在執(zhí)行第二個(gè)宏任務(wù)時(shí),微任務(wù)隊(duì)列中已經(jīng)存在待處理的,因此需要先執(zhí)行微任務(wù)。
微任務(wù)執(zhí)行完畢,并且延遲時(shí)間到期,第一個(gè)setTimeout開始執(zhí)行。遇到Promise,將其添加到微任務(wù)隊(duì)列中
執(zhí)行微任務(wù)隊(duì)列中的Promise,執(zhí)行完畢后遇到then,則將其繼續(xù)添加到微任務(wù)隊(duì)列
直到所有微任務(wù)執(zhí)行完畢
宏任務(wù)中創(chuàng)建宏任務(wù)
setTimeout(() => { console.log('setTimeout 1'); setTimeout(() => { console.log('setTimeout 2'); }, 500); setTimeout(() => { console.log('setTimeout 3'); }, 500); setTimeout(() => { console.log('setTimeout 4'); }, 100); }, 0);
運(yùn)行結(jié)果
setTimeout 1
setTimeout 4
setTimeout 2
setTimeout 3
解析
宏任務(wù)中創(chuàng)建宏任務(wù),執(zhí)行順序一般來說是按照先后順序的。對(duì)于setTImeout來說,延遲時(shí)間相同,則按照先后順序執(zhí)行;延遲時(shí)間不同,則按照延遲時(shí)間的大小先后順序執(zhí)行
微任務(wù)中創(chuàng)建微任務(wù)
new Promise((resolve) => { console.log('promise 1'); new Promise((resolve) => { console.log('promise 2'); resolve(); }); new Promise((resolve) => { console.log('promise 3'); resolve(); }) resolve(); })
運(yùn)行結(jié)果
promise 1
promise 2
promise 3
解析
微任務(wù)中創(chuàng)建微任務(wù),執(zhí)行順序一般來說是按照先后順序執(zhí)行的。
總結(jié)
- 同步代碼直接執(zhí)行,異步代碼添加到宏任務(wù)隊(duì)列或者微任務(wù)隊(duì)列
- 微任務(wù)在下一輪DOM渲染前執(zhí)行,宏任務(wù)在下一輪DOM渲染之后執(zhí)行
- 事件循環(huán)持續(xù)監(jiān)聽
- 如果存在異步操作,需要將關(guān)聯(lián)代碼放在異步函數(shù)中執(zhí)行
- 如果代碼層次比較復(fù)雜,同步、異步代碼混雜,一定要理清代碼的執(zhí)行順序。避免因?yàn)楫惒?,?dǎo)致代碼出現(xiàn)難以察覺的bug
到此這篇關(guān)于詳解JS事件循環(huán)及宏任務(wù)微任務(wù)的原理的文章就介紹到這了,更多相關(guān)JS宏任務(wù) 微任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序項(xiàng)目總結(jié)之記賬小程序功能的實(shí)現(xiàn)(包括后端)
這篇文章主要介紹了微信小程序項(xiàng)目總結(jié)之記賬小程序功能的實(shí)現(xiàn)方法(包括后端),需要的朋友可以參考下2019-08-08用客戶端js實(shí)現(xiàn)帶省略號(hào)的分頁
帶省略號(hào)的分頁只有在服務(wù)器端才可以實(shí)現(xiàn),下面為大家介紹的是用js實(shí)現(xiàn)的帶省略號(hào)的分頁,感興趣的朋友可以參考下哈,希望對(duì)你寫出好的分頁有所幫助2013-04-04JavaScript+H5實(shí)現(xiàn)微信搖一搖功能
這篇文章主要為大家詳細(xì)介紹了JavaScript+H5實(shí)現(xiàn)微信搖一搖功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05PHP實(shí)現(xiàn)的各種中文編碼轉(zhuǎn)換類分享
這篇文章主要介紹了PHP實(shí)現(xiàn)的各種中文編碼轉(zhuǎn)換類分享,本文類庫支持簡(jiǎn)體中文、繁體中文、GB2312、BIG5、UTF-8等多種格式之間的轉(zhuǎn)換,需要的朋友可以參考下2015-01-01javascript省市級(jí)聯(lián)功能實(shí)現(xiàn)方法實(shí)例詳解
這篇文章主要介紹了javascript省市級(jí)聯(lián)功能實(shí)現(xiàn)方法,以不同實(shí)例形式分析了JavaScript實(shí)現(xiàn)省市級(jí)聯(lián)菜單的具體技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10js的math中缺少的數(shù)學(xué)方法小結(jié)
JavaScript?Math對(duì)象包含一些真正有用且強(qiáng)大的數(shù)學(xué)運(yùn)算,但它缺乏大多數(shù)其他語言提供的許多重要運(yùn)算,例如求和,乘積,奇數(shù)和偶數(shù)等等,本文就來介紹一下2023-08-08JS數(shù)據(jù)結(jié)構(gòu)之隊(duì)列結(jié)構(gòu)詳解
這篇文章主要為大家詳細(xì)介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)與算法中的隊(duì)列結(jié)構(gòu),文中通過簡(jiǎn)單的示例介紹了隊(duì)列結(jié)構(gòu)的原理與實(shí)現(xiàn),需要的可以參考一下2022-11-11