前言
Event Loop即事件循環(huán)飞主,是指瀏覽器或Node的一種解決javaScript單線程運(yùn)行時(shí)不會(huì)阻塞的一種機(jī)制座每,也就是我們經(jīng)常使用異步的原理前鹅。
為啥要弄懂Event Loop
是要增加自己技術(shù)的深度,也就是懂得JavaScript的運(yùn)行機(jī)制峭梳。
現(xiàn)在在前端領(lǐng)域各種技術(shù)層出不窮舰绘,掌握底層原理,可以讓自己以不變延赌,應(yīng)萬變除盏。
應(yīng)對(duì)各大互聯(lián)網(wǎng)公司的面試叉橱,懂其原理挫以,題目任其發(fā)揮。
堆窃祝,棧掐松、隊(duì)列
堆(Heap)
堆是一種數(shù)據(jù)結(jié)構(gòu),是利用完全二叉樹維護(hù)的一組數(shù)據(jù)粪小,堆分為兩種大磺,一種為最大堆,一種為最小堆探膊,將根節(jié)點(diǎn)最大的堆叫做最大堆或大根堆杠愧,根節(jié)點(diǎn)最小的堆叫做最小堆或小根堆。
堆是線性數(shù)據(jù)結(jié)構(gòu)逞壁,相當(dāng)于一維數(shù)組流济,有唯一后繼。
如最大堆
棧(Stack)(后進(jìn)先出)
棧在計(jì)算機(jī)科學(xué)中是限定僅在表尾進(jìn)行插入或刪除操作的線性表腌闯。?棧是一種數(shù)據(jù)結(jié)構(gòu)绳瘟,它按照后進(jìn)先出的原則存儲(chǔ)數(shù)據(jù),先進(jìn)入的數(shù)據(jù)被壓入棧底姿骏,最后的數(shù)據(jù)在棧頂糖声,需要讀數(shù)據(jù)的時(shí)候從棧頂開始彈出數(shù)據(jù)。
棧是只能在某一端插入和刪除的特殊線性表。
隊(duì)列(Queue)(先進(jìn)先出)
特殊之處在于它只允許在表的前端(front)進(jìn)行刪除操作蘸泻,而在表的后端(rear)進(jìn)行插入操作琉苇,和棧一樣,隊(duì)列是一種操作受限制的線性表悦施。
進(jìn)行插入操作的端稱為隊(duì)尾翁潘,進(jìn)行刪除操作的端稱為隊(duì)頭。? 隊(duì)列中沒有元素時(shí)歼争,稱為空隊(duì)列拜马。
隊(duì)列的數(shù)據(jù)元素又稱為隊(duì)列元素。在隊(duì)列中插入一個(gè)隊(duì)列元素稱為入隊(duì)沐绒,從隊(duì)列中刪除一個(gè)隊(duì)列元素稱為出隊(duì)俩莽。因?yàn)殛?duì)列只允許在一端插入,在另一端刪除乔遮,所以只有最早進(jìn)入隊(duì)列的元素才能最先從隊(duì)列中刪除扮超,故隊(duì)列又稱為先進(jìn)先出(FIFO—first in first out)
Event Loop
在JavaScript中,任務(wù)被分為兩種蹋肮,一種宏任務(wù)(MacroTask)也叫Task出刷,一種叫微任務(wù)(MicroTask)。
MacroTask(宏任務(wù))
script全部代碼坯辩、setTimeout馁龟、setInterval、setImmediate(瀏覽器暫時(shí)不支持漆魔,只有IE10支持坷檩,具體可見MDN)、I/O改抡、UI Rendering矢炼。
MicroTask(微任務(wù))
Process.nextTick(Node獨(dú)有)、Promise阿纤、Object.observe(廢棄)句灌、MutationObserver(具體使用方式查看這里)
瀏覽器中的Event Loop
Javascript?有一個(gè)?main thread?主線程和?call-stack?調(diào)用棧(執(zhí)行棧),所有的任務(wù)都會(huì)被放到調(diào)用棧等待主線程執(zhí)行欠拾。
JS調(diào)用棧
JS調(diào)用棧采用的是后進(jìn)先出的規(guī)則胰锌,當(dāng)函數(shù)執(zhí)行的時(shí)候,會(huì)被添加到棧的頂部清蚀,當(dāng)執(zhí)行棧執(zhí)行完成后匕荸,就會(huì)從棧頂移出,直到棧內(nèi)被清空枷邪。
同步任務(wù)和異步任務(wù)
Javascript單線程任務(wù)被分為同步任務(wù)和異步任務(wù)榛搔,同步任務(wù)會(huì)在調(diào)用棧中按照順序等待主線程依次執(zhí)行诺凡,異步任務(wù)會(huì)在異步任務(wù)有了結(jié)果后,將注冊(cè)的回調(diào)函數(shù)放入任務(wù)隊(duì)列中等待主線程空閑的時(shí)候(調(diào)用棧被清空)践惑,被讀取到棧內(nèi)等待主線程的執(zhí)行腹泌。
任務(wù)隊(duì)列Task Queue,即隊(duì)列尔觉,是一種先進(jìn)先出的一種數(shù)據(jù)結(jié)構(gòu)凉袱。
事件循環(huán)的進(jìn)程模型
選擇當(dāng)前要執(zhí)行的任務(wù)隊(duì)列,選擇任務(wù)隊(duì)列中最先進(jìn)入的任務(wù)侦铜,如果任務(wù)隊(duì)列為空即null专甩,則執(zhí)行跳轉(zhuǎn)到微任務(wù)(MicroTask)的執(zhí)行步驟。
將事件循環(huán)中的任務(wù)設(shè)置為已選擇任務(wù)钉稍。
執(zhí)行任務(wù)涤躲。
將事件循環(huán)中當(dāng)前運(yùn)行任務(wù)設(shè)置為null。
將已經(jīng)運(yùn)行完成的任務(wù)從任務(wù)隊(duì)列中刪除贡未。
microtasks步驟:進(jìn)入microtask檢查點(diǎn)种樱。
更新界面渲染。
返回第一步俊卤。
執(zhí)行進(jìn)入microtask檢查點(diǎn)時(shí)嫩挤,用戶代理會(huì)執(zhí)行以下步驟:
設(shè)置microtask檢查點(diǎn)標(biāo)志為true。
當(dāng)事件循環(huán)microtask執(zhí)行不為空時(shí):選擇一個(gè)最先進(jìn)入的microtask隊(duì)列的microtask消恍,將事件循環(huán)的microtask設(shè)置為已選擇的microtask岂昭,運(yùn)行microtask,將已經(jīng)執(zhí)行完成的microtask為null哺哼,移出microtask中的microtask佩抹。
清理IndexDB事務(wù)
設(shè)置進(jìn)入microtask檢查點(diǎn)的標(biāo)志為false。
執(zhí)行棧在執(zhí)行完同步任務(wù)后取董,查看執(zhí)行棧是否為空,如果執(zhí)行棧為空无宿,就會(huì)去檢查微任務(wù)(microTask)隊(duì)列是否為空茵汰,如果為空的話,就執(zhí)行Task(宏任務(wù))孽鸡,否則就一次性執(zhí)行完所有微任務(wù)蹂午。
每次單個(gè)宏任務(wù)執(zhí)行完畢后,檢查微任務(wù)(microTask)隊(duì)列是否為空彬碱,如果不為空的話豆胸,會(huì)按照先入先出的規(guī)則全部執(zhí)行完微任務(wù)(microTask)后,設(shè)置微任務(wù)(microTask)隊(duì)列為null巷疼,然后再執(zhí)行宏任務(wù)晚胡,如此循環(huán)。
舉個(gè)例子
console.log('script start');
setTimeout(function() {
? console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
? console.log('promise1');
}).then(function() {
? console.log('promise2');
});
console.log('script end');
首先我們劃分幾個(gè)分類:
第一次執(zhí)行:
Tasks:run script、 setTimeout callback
Microtasks:Promise then
JS stack: script
Log: script start估盘、script end瓷患。
執(zhí)行同步代碼,將宏任務(wù)(Tasks)和微任務(wù)(Microtasks)劃分到各自隊(duì)列中遣妥。
第二次執(zhí)行:
Tasks:run script擅编、 setTimeout callback
Microtasks:Promise2 then
JS stack: Promise2 callback
Log: script start、script end箫踩、promise1爱态、promise2
執(zhí)行同步代碼后,檢測(cè)到微任務(wù)(Microtasks)隊(duì)列中不為空境钟,執(zhí)行Promise1肢藐,執(zhí)行完成Promise1后,調(diào)用Promise2.then吱韭,放入微任務(wù)(Microtasks)隊(duì)列中吆豹,再執(zhí)行Promise2.then。
第三次執(zhí)行:
Tasks:setTimeout callback
Microtasks:
JS stack:
Log: script start理盆、script end痘煤、promise1、promise2猿规、setTimeout
當(dāng)微任務(wù)(Microtasks)隊(duì)列中為空時(shí)衷快,執(zhí)行宏任務(wù)(Tasks),執(zhí)行setTimeout callback姨俩,打印日志蘸拔。
第四次執(zhí)行:
Tasks:setTimeout callback
Microtasks:
JS stack:
Log: script start、script end环葵、promise1调窍、promise2、setTimeout
清空Tasks隊(duì)列和JS stack张遭。
以上執(zhí)行幀動(dòng)畫可以查看Tasks, microtasks, queues and schedules
再舉個(gè)例子
console.log('script start')
async function async1() {
? await async2()
? console.log('async1 end')
}
async function async2() {
? console.log('async2 end')
}
async1()
setTimeout(function() {
? console.log('setTimeout')
}, 0)
new Promise(resolve => {
? console.log('Promise')
? resolve()
})
? .then(function() {
? ? console.log('promise1')
? })
? .then(function() {
? ? console.log('promise2')
? })
console.log('script end')
這里需要先理解async/await邓萨。
async/await在底層轉(zhuǎn)換成了promise和then回調(diào)函數(shù)。
也就是說菊卷,這是promise的語法糖缔恳。
每次我們使用await, 解釋器都創(chuàng)建一個(gè)promise對(duì)象,然后把剩下的async函數(shù)中的操作放到then回調(diào)函數(shù)中洁闰。
async/await的實(shí)現(xiàn)歉甚,離不開Promise。從字面意思來理解扑眉,async是“異步”的簡(jiǎn)寫纸泄,而await是async wait的簡(jiǎn)寫可以認(rèn)為是等待異步方法執(zhí)行完成赖钞。