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