摘要:何為事件循環(huán)機(jī)制的任務(wù)分兩種,分別是同步任務(wù)和異步任務(wù)。如上圖所示主線程在執(zhí)行代碼的時(shí)候,遇到異步任務(wù)進(jìn)入并注冊(cè)回調(diào)函數(shù),有了運(yùn)行結(jié)果后將它添加到事件隊(duì)列中,然后繼續(xù)執(zhí)行下面的代碼,直到同步代碼執(zhí)行完。
我們知道,JavaScript作為瀏覽器的腳本語言,起初是為了與用戶交互和操作DOM,為了避免因?yàn)橥瑫r(shí)操作了同一DOM節(jié)點(diǎn)而引起沖突,被設(shè)計(jì)成為一種單線程語言。何為事件循環(huán)機(jī)制?而單線程語言最大的特性就是同一時(shí)間只能做一件事,這個(gè)任務(wù)未完成下一個(gè)任務(wù)就要等待,這樣無疑是對(duì)資源的極大浪費(fèi),而且嚴(yán)重時(shí)會(huì)引起阻塞,造成用戶體驗(yàn)極差。這個(gè)時(shí)候就引出了異步的概念,而異步的核心就是事件循環(huán)機(jī)制Event Loop。
JavaScript的任務(wù)分兩種,分別是同步任務(wù)和異步任務(wù)。
同步任務(wù):在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù);
異步任務(wù):不進(jìn)入主線程而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。
如上圖所示:
主線程在執(zhí)行代碼的時(shí)候,遇到異步任務(wù)進(jìn)入Event Table并注冊(cè)回調(diào)函數(shù),有了運(yùn)行結(jié)果后將它添加到事件隊(duì)列(callback queue)中,然后繼續(xù)執(zhí)行下面的代碼,直到同步代碼執(zhí)行完。
主線程執(zhí)行完同步代碼后,讀取callback queue中的任務(wù),如果有可執(zhí)行任務(wù)則進(jìn)入主線程執(zhí)行
不斷重復(fù)以上步驟,就形成了事件循環(huán)(Event Loop)
結(jié)合上面步驟分析下這個(gè)例子:
1. 執(zhí)行主線程同步任務(wù),輸出start【1】,繼續(xù)往下執(zhí)行 2. 遇到setTimeout,進(jìn)入event table注冊(cè)setTimeout回調(diào),setTimeout回調(diào)執(zhí)行完后,繼續(xù)往下執(zhí)行 3. 輸出end【2】,同步任務(wù)執(zhí)行完畢 4. 進(jìn)入event queue,檢查是否有可執(zhí)行任務(wù),取出event queue中setTimeout任務(wù)開始執(zhí)行,輸出setTimeout【3】
結(jié)果依次為:start -> end -> setTimeout
瀏覽器環(huán)境下的異步任務(wù)在瀏覽器和node中的事件循環(huán)與執(zhí)行機(jī)制是不同的,要注意區(qū)分,不要搞混。執(zhí)行過程
瀏覽器環(huán)境的異步任務(wù)分為宏任務(wù)(macroTask)和微任務(wù)(microtask),當(dāng)滿足條件時(shí)會(huì)分別被放進(jìn)宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列(先進(jìn)先出),等待被執(zhí)行。
微任務(wù):
promise,MutationObserver
宏任務(wù):
script整體,setTimeout & setIntervat,I/O,UI render。
執(zhí)行過程如下:
如圖所示:
1. 把整體的script代碼作為宏任務(wù)執(zhí)行 2. 執(zhí)行過程中如果遇到宏任務(wù)和微任務(wù),滿足條件時(shí)分別添加至宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列 3. 執(zhí)行完一個(gè)宏任務(wù)后,取出所有微任務(wù)依次執(zhí)行,如果微任務(wù)一直有新的被添加進(jìn)來,則一直執(zhí)行,直到把微任務(wù)隊(duì)列清空 4. 不斷重復(fù)2和3,直到所有任務(wù)被清空,結(jié)束執(zhí)行。
分析:
第一輪:
輸出start【1】,將setTimeout回調(diào)函數(shù)@1,放進(jìn)宏任務(wù)隊(duì)列;
將setTimeout回調(diào)函數(shù)@2,放進(jìn)宏任務(wù)隊(duì)列;
將setTimeout回調(diào)函數(shù)@3,放進(jìn)宏任務(wù)隊(duì)列;
執(zhí)行new Promise函數(shù)輸出promise4【2】,將Promise.then@1放進(jìn)微任務(wù)隊(duì)列;
輸出end【3】,此時(shí)隊(duì)列如下所示:
第一輪宏任務(wù)執(zhí)行完畢,開始執(zhí)行微任務(wù),取出微任務(wù)Promise.then@1,輸出promise5【4】,此時(shí)微任務(wù)隊(duì)列被清空,開始第二輪執(zhí)行。
第二輪:
取出宏任務(wù)setTimeout回調(diào)函數(shù)@1,輸出timer1【5】,將回調(diào)函數(shù)中的Promise.then@2放進(jìn)微任務(wù)隊(duì)列;
宏任務(wù)setTimeout回調(diào)函數(shù)@1中無宏任務(wù),開始執(zhí)行微任務(wù),取出Promise.then@2,輸出promise1【6】,此時(shí):
setTimeout回調(diào)函數(shù)@1中宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列均被清空,開始第三輪執(zhí)行
第三輪:
取出宏任務(wù)setTimeout回調(diào)函數(shù)@2,輸出timer2【7】,將Promise.then@3放進(jìn)微任務(wù)隊(duì)列;
setTimeout回調(diào)函數(shù)@2中無宏任務(wù),開始執(zhí)行微任務(wù),取出Promise.then@3,輸出promise2【8】,此時(shí):
宏任務(wù)setTimeout回調(diào)函數(shù)@2中宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列均被清空,開始第四輪執(zhí)行
第四輪:
取出宏任務(wù)setTimeout回調(diào)函數(shù)@3,輸出timer3【9】,將Promise.then@4放進(jìn)微任務(wù)隊(duì)列;
setTimeout回調(diào)函數(shù)@3中無宏任務(wù),開始執(zhí)行微任務(wù),取出Promise.then@4,輸出promise3【10】
現(xiàn)在宏任務(wù)對(duì)列和微任務(wù)隊(duì)列都被清空了,完成執(zhí)行,結(jié)果為:start > promise4 > end > promise5 > timer1 > promise1 > timer2 > promise2 > timer3 > promise3
引入 async/awaitasnyc知識(shí)點(diǎn)傳送門
await表達(dá)式的運(yùn)算結(jié)果取決于它右側(cè)的結(jié)果
當(dāng)遇到await時(shí),會(huì)阻塞函數(shù)體內(nèi)部處于await后面的代碼,跳出去執(zhí)行該函數(shù)外部的同步代碼,當(dāng)外部同步代碼執(zhí)行完畢,再回到該函數(shù)內(nèi)部執(zhí)行剩余的代碼
補(bǔ)充aynsc的一點(diǎn)知識(shí):如果aynsc函數(shù)中return一個(gè)直接量,async 會(huì)把這個(gè)直接量通過Promise.resolve()封裝成Promise對(duì)象,如果什么都沒return,會(huì)被封裝成Promise.resolve(undefined)
那么 引入了async await之后的執(zhí)行過程是怎樣的呢?
分析:
第一輪:
執(zhí)行同步代碼,輸出:script start【1】,將setTimeout回調(diào)@1放入宏任務(wù)隊(duì)列;
進(jìn)入aynsc1函數(shù)中,執(zhí)行同步代碼輸出:async1 start【2】,遇到await從右向左執(zhí)行,進(jìn)入async2函數(shù),輸出:async2【3】;aynsc2函數(shù)體中未返回任何東西等價(jià)于返回了Promise.resolve(undefined),拿到返回值后進(jìn)入aynsc1函數(shù)體中,繼續(xù)執(zhí)行剩下的部分,這時(shí)候aynsc1中注釋部分等價(jià)于:
async function async1() { console.log("async1 start"); //await async2(); //console.log("async1 end"); await new Promise((resolve) => resolve()).then(resolve => { console.log("async1 end") }) }
將Promise.then@1推入到微任務(wù)隊(duì)列;
繼續(xù)執(zhí)行同步代碼,輸出:promise1【4】,將Promise.then@2推入微任務(wù)隊(duì)列
繼續(xù)執(zhí)行同步代碼,輸出:script end【5】,第一輪宏隊(duì)列任務(wù)執(zhí)行完畢,此時(shí)如下:
開始執(zhí)行微任務(wù),取出微任務(wù)Promise.then@1,值為undefined,這個(gè)時(shí)候Promise.then@1完成執(zhí)行,則await aynsc2()得到了值也完成了執(zhí)行,不再阻塞后面代碼,那么執(zhí)行同步代碼輸出:async1 end【6】;
取出微任務(wù)Promise.then@2,輸出:promise2【7】,微任務(wù)全部執(zhí)行完畢,現(xiàn)在開始第二輪執(zhí)行
第二輪:
取出宏任務(wù)隊(duì)列中的setTimeout@1,輸出setTimeout【8】
所有任務(wù)隊(duì)列均為空,結(jié)束執(zhí)行,輸出結(jié)果為:script start > async1 start > async2 > promise1 > script end > async1 end > promise2 > setTimeout
補(bǔ)充谷歌瀏覽器測(cè)試結(jié)果:
借用一個(gè)例子:await一個(gè)直接值的情況
分析:
第一輪:
執(zhí)行同步函數(shù),輸出:1【1】,進(jìn)入async1函數(shù)中,輸出:2【2】,這個(gè)時(shí)候await雖然接收了一個(gè)直接值,但是還是要先執(zhí)行外邊的同步代碼之后才能執(zhí)行await后邊的值
繼續(xù)執(zhí)行同步代碼,輸出:3【3】,進(jìn)入Promise函數(shù),輸出:4【4】,將Promise.then推入微任務(wù)隊(duì)列
同步代碼執(zhí)行完畢,進(jìn)入 async1函數(shù)中輸出:5【5】
宏任務(wù)執(zhí)行完畢,進(jìn)入微任務(wù)隊(duì)列,開始執(zhí)行微任務(wù);取出Promise.then,輸出:6【6】
任務(wù)隊(duì)列為空,執(zhí)行完畢,結(jié)果為: 1 > 2 > 3 > 4 > 5 > 6
再借個(gè)例子,這個(gè)有點(diǎn)復(fù)雜
分析:
第一輪:
將setTimeOut@1放入宏任務(wù)列隊(duì);
執(zhí)行async1()函數(shù)體內(nèi)的函數(shù),輸出:1【1】,遇到await,進(jìn)入aynsc2函數(shù)體,輸出:2【2】,將該函數(shù)體內(nèi)promise.then@1放入微任務(wù)隊(duì)列中;
執(zhí)行New promise .. 輸出3【3】,將該函數(shù)體內(nèi)Promise.then@2放入微任務(wù)隊(duì)列中,第一輪宏任務(wù)執(zhí)行完畢,此時(shí):
開始執(zhí)行第一輪微任務(wù),取出Promise.then@1,輸出:4【4】,此時(shí)async2函數(shù)執(zhí)行完畢,進(jìn)入aynsc1函數(shù),此時(shí)改動(dòng)下aynsc1函數(shù),等價(jià)于:
async function async1() { console.log("1") //const data = await async2() //console.log("6") const data = await new Promise(resolve => resolve("async2的結(jié)果")).then((resolve) => { console.log(6); return resolve; }) return data; }
將上面promise.then@3推入微任務(wù)隊(duì)列中,此時(shí):
接著執(zhí)行微任務(wù),取出promise.then@2,輸出:5【5】,取出promise.then@3,輸出:6【6】,此時(shí)函數(shù)async1執(zhí)行完成,接著執(zhí)行async1().then(...),將async1().then@1推到微任務(wù)隊(duì)列中,取出async1().then@1,輸出:7【7】和 "async2的結(jié)果"【8】;
第一輪任務(wù)執(zhí)行完畢,開始執(zhí)行第二輪,此時(shí):
第二輪:
開始執(zhí)行第二輪宏任務(wù),將setTimeOut@1取出執(zhí)行,輸出8【9】,完畢。
所以任務(wù)被執(zhí)行完畢,結(jié)果為:1 > 2 > 3 > 4 > 5 > 6 > 7 > async2的結(jié)果 > 8
------------------------ END ----------------------------
PS: 好記性不如爛筆頭,看了那么多資料,還是想總結(jié)一下,不然過一陣子就忘記了,如果辛苦指出哦,謝謝~
參考資料:
理解 JavaScript 的 async/await
瀏覽器和Node不同的事件循環(huán)(Event Loop)
Event Loop 原來是這么回事
這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制
從event loop到async await來了解事件循環(huán)機(jī)制
...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/102424.html
摘要:深入理解引擎的執(zhí)行機(jī)制靈魂三問為什么是單線程的為什么需要異步單線程又是如何實(shí)現(xiàn)異步的呢中的中的說說首先請(qǐng)牢記點(diǎn)是單線程語言的是的執(zhí)行機(jī)制。 深入理解JS引擎的執(zhí)行機(jī)制 1.靈魂三問 : JS為什么是單線程的? 為什么需要異步? 單線程又是如何實(shí)現(xiàn)異步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.說說setTimeout 首先,請(qǐng)牢記2...
摘要:機(jī)制詳解與中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代開發(fā)語法基礎(chǔ)與實(shí)踐技巧系列文章。事件循環(huán)機(jī)制詳解與實(shí)踐應(yīng)用是典型的單線程單并發(fā)語言,即表示在同一時(shí)間片內(nèi)其只能執(zhí)行單個(gè)任務(wù)或者部分代碼片。 JavaScript Event Loop 機(jī)制詳解與 Vue.js 中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代 JavaScript 開發(fā):語法基礎(chǔ)與實(shí)踐技巧系列文章。本文依次介紹了函數(shù)調(diào)用棧、MacroTask 與 Micr...
摘要:上代碼代碼可以看出,不僅函數(shù)比指定的回調(diào)函數(shù)先執(zhí)行,而且函數(shù)也比先執(zhí)行。這是因?yàn)楹笠粋€(gè)事件進(jìn)入的時(shí)候,事件環(huán)可能處于不同的階段導(dǎo)致結(jié)果的不確定。這是因?yàn)橐驗(yàn)閳?zhí)行完后,程序設(shè)定了和,因此階段不會(huì)被阻塞進(jìn)而進(jìn)入階段先執(zhí)行,后進(jìn)入階段執(zhí)行。 JavaScript(簡(jiǎn)稱JS)是前端的首要研究語言,要想真正理解JavaScript就繞不開他的運(yùn)行機(jī)制--Event Loop(事件環(huán)) JS是一門...
摘要:二瀏覽器端在講解事件循環(huán)之前先談?wù)勚型酱a異步代碼的執(zhí)行流程。三端我自己認(rèn)為的事件循環(huán)和瀏覽器端還是有點(diǎn)區(qū)別的,它的事件循環(huán)依靠引擎。四總結(jié)本篇主要介紹了瀏覽器和對(duì)于事件循環(huán)機(jī)制實(shí)現(xiàn),由于能力水平有限,其中可能有誤之處歡迎指出。 一、前言 前幾天聽公司一個(gè)公司三年的前端說今天又學(xué)到了一個(gè)知識(shí)點(diǎn)-微任務(wù)、宏任務(wù),我問他這是什么東西,由于在吃飯他淺淺的說了下,當(dāng)時(shí)沒太理解就私下學(xué)習(xí)整理一...
摘要:前言是以單線程的形式運(yùn)行在宿主環(huán)境下,采用了回調(diào)的形式來解決異步任務(wù)。線程中步就是在瀏覽器下的。 前言 javascript 是以單線程的形式運(yùn)行在宿主環(huán)境下,javascript 采用了回調(diào)的形式來解決異步任務(wù)。 為什么是單線程? javascript 的最開始的出現(xiàn)是為了給 web 頁(yè)面增添一些動(dòng)態(tài)的效果,那么就避免不了獲取頁(yè)面上的元素信息,如果 javascript 是以多線程的...
閱讀 3636·2023-04-25 14:20
閱讀 1278·2021-09-10 10:51
閱讀 1202·2019-08-30 15:53
閱讀 509·2019-08-30 15:43
閱讀 2371·2019-08-30 14:13
閱讀 2844·2019-08-30 12:45
閱讀 1257·2019-08-29 16:18
閱讀 1227·2019-08-29 16:12