發布時間:2025-06-30閱讀(16)
上一篇炒冷飯系列1:一道字節面試題引出的this指向問題開啟了炒冷飯系列,想必很多人不知道炒冷飯的真正含義,那這里就引用一下百度百科的解釋。
炒冷飯是一個網絡流行語,意思是比喻重復已經說過的話或做過的事,沒有新的內容。
逛掘金的都知道,平臺有很多文章的主題都是寫了又寫,在一些用戶看來就是一直在冷飯熱炒的感覺;感覺沒有其他內容主題可以寫了,其實不然,冷飯熱炒也是一門學問,畢竟每個人對每個事物的認知都是不一樣的,你有你的見解我有我的看法,只是在別人寫的時候你對其還沒有一定的認知罷了。
所以我就干脆開啟一個炒冷飯系列,但是此冷飯非彼冷飯,我的冷飯取材于面試或者工作中遇到的一些自己掌握不牢的知識點,而不是包羅萬象地介紹全部,其實就是一個視自身掌握情況來決定是否冷飯熱炒的系列。
背景同樣,這次還是由一道字節的面試引出要介紹的主題,還是上篇文章說的,真的是準備不足而不是別人問得深入、基礎。所以再次提醒面試大廠一定要好好準備,不然真的機會渺茫啊。題目還是一道代碼題,要求你說出打印什么,為什么?
setTimeout(() => { console.log(1);}, 0)new Promise((resolve) => { console.log(2); for(let i = 0;i<10000;i ){ if(i === 9999){ resolve(); } } console.log(3);}).then(() => { console.log(4);})console.log(5);題目就是這樣的,其實真的不難,你可以試著去分析一下,如果覺得拿不準結果,那就耐心看完此文之后再來回看,相信那時你應該就能十拿九穩了。接下來就由這道面試引出這篇文章的主題:Js的事件循環機制,如果你很了解這個主題那就選擇略過,否則就一起往下看看,這是面試題必考的點!
Javascript事件循環一、概念眾所周知,為了與瀏覽器進行交互,Javascript是一門非阻塞單線程的腳本語言。怎么去理解?
- 先來說說為什么是單線程?
在DOM操作中,如果有一個添加節點線程和一個刪除節點的線程,瀏覽器并不知道以哪個為準,所以只能選擇一個線程來執行代碼,從而防止沖突。
- 再來說說為什么是非阻塞?
單線程就意味著任務需要排隊,按順序執行。如果某一任務很耗時,那后面的任務不得不排隊等待,所以為了避免這種阻塞,就需要一種非阻塞機制。這種非阻塞機制就是異步機制,即需要等待的任務不會阻塞主線程中同步任務的執行。
既然主要的原因知道了,那就接著說說一些主要的概念然后再介紹具體的事件循環執行問題。
1.1 瀏覽器執行線程Js是單線程的腳本語言,但是瀏覽器是多進程的。瀏覽器的每一個tab標簽頁都代表一個獨立的進程,其中瀏覽器渲染進程也只屬于瀏覽器多進程中的其中一種,其主要負責頁面渲染,腳本執行,事件處理等。
瀏覽器進程還包含有以下主要線程:GUI渲染線程、JS引擎線程、事件觸發線程、定時器觸發線程 和 HTTP請求線程等。
進程是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。
(注意:一個進程掛掉不會影響其他進程,但是一個線程掛掉將導致整個進程掛掉)
主線程指的是JS引擎執行的線程。這個線程只有一個,頁面渲染、函數處理都在這個主線程上執行。
工作線程這個線程可能存在于瀏覽器或JS引擎內,與主線程是分開的;處理文件讀取、網絡請求等異步事件。
1.2 同步任務和異步任務同步任務指的是在主線程上排隊執行的那些任務。只有前一個任務執行完畢,才能執行后一個任務。
異步任務指的是由Javascript委托給宿主環境(瀏覽器或Node.js)進行執行的任務。當異步任務執行完成后,會通知JavaScript主線程執行異步任務的回調函數。
舉個例子:一個人吃完飯后看手機,看手機后聊八卦,這樣按照順序一步步來,等上一件事完了之后再執行后面事情的就是同步方式;而一個人在吃飯的同時,可以看手機和聊八卦,這樣能做很多件事情的就是異步方式。
1.3 任務隊列任務隊列主要分兩種:宏任務和微任務。
宏任務:每次執行棧執行的代碼。常見的宏任務主要包含:script(整體代碼)、setTimeout、setInterval、I/O、異步Ajax、setImmediate(Node.js環境)等。
微任務:當前宏任務執行結束后立即執行的任務。常見的微任務主要包含:Promise.then(catch、finally)、MutaionObserver、process.nextTick(Node.js環境)等。
好了,到這里事件循環機制里面相關的概念已在上面列出解釋了。其實通過上面的介紹可以得到下面這樣一個圖示:
二、事件循環機制執行流程
任務進入執行棧,就會根據任務類型判斷是同步任務還是異步任務,然后分別進入不同的執行環境,同步任務進入主線程,異步任務進入任務隊列。主線程內的任務執行完畢后,回去任務隊列讀取對應的任務,推入主線程執行。上述過程不斷重復就是事件循環。
任務隊列執行順序
每一個宏任務執行完之后,都會檢查是否存在待執行的微任務;如果有,則執行完所有微任務后再繼續執行下一個宏任務;反之沒有就直接執行下一個宏任務。
根據上面圖示可以得到事件循環機制的關鍵步驟,如下:
- 執行一個宏任務(棧中沒有就從任務隊列中讀?。?;
- 執行過程中若遇到微任務,就將它添加到微任務的任務隊列中;
- 宏任務執行完畢后,立即執行當前微任務隊列中的所有微任務(依次執行);
- 當前宏任務執行完畢,開始下一個宏任務(從任務隊列中獲取);
- 這樣往復循環。
好啦,JS事件循環的相關概念和流程順序就介紹完了。掌握概念之后,來看一些例子加深一下→
三、示例回到一開始提到面試題,那就看看它的執行結果是怎樣的,分析如下:
- 遇到setTimeout,屬于新的宏任務,留著后面執行
- 遇到new Promise,這個是直接執行的,打印23
- Promise的then屬于微任務,放在微任務隊列
- 遇到console.log(5),直接打印5
- 好了本輪宏任務執行完畢,去微任務列表查看是否有微任務,發現Promise.then的回調,執行打印4
- 當一次宏任務執行完,再去執行新的宏任務,里面就剩一個定時器任務,執行打印1
- 最后結果為:2 3 5 4 1
下面來一個例子試試水:
console.log(1);setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) });}, 0);new Promise((resolve, reject) => { console.log(4); setTimeout(() => { console.log(5); resolve(6); }, 0);}).then(res => { console.log(7); setTimeout(() => { console.log(res); }, 0);});分析過程,如下:
- 代碼開始主任務中的第一輪宏任務,遇到console.log(1),直接打印1
- 遇到setTimout定時器,它是新的宏任務,先放著不執行
- 遇到new Promise,直接執行,打印4
- 又遇到定時器,放入宏任務隊列中,先不執行
- 第一輪宏任務就執行完了,且沒有微任務。問:Promise后的.then是第一輪宏任務的微任務么?不是!因為resolve都沒有執行,promise的狀態還是pending,故就不是第一輪的微任務
- 開始下一輪宏任務,執行第一個setTimeout,打印2,第二輪宏任務結束
- 遇到第一個setTimeout內的promise.then,開始執行微任務,直接打印3,第二輪宏任務結束
- 接著開始第二個setTimeout宏任務,打印5,resolve()在這里執行了
- 遇到Promise.then放入微任務隊列,執行微任務打印7,遇到第三個setTimeout,放入宏任務隊列
- 又開始第三個setTimeout宏任務,打印6
- 最后結果為:1 4 2 3 5 7 6
下面來一個經典的例子:
async function async1() { console.log(1); await async2(); console.log(2);}async function async2() { console.log(3);}console.log(4);setTimeout(function() { console.log(5);});async1();new Promise((resolve) => { console.log(6); resolve();}).then(() => { console.log(7);});console.log(8);分析過程,如下:
- 代碼開始執行,async是異步的意思,返回的也是promise對象,但是它沒有執行,直接跳過async1和async2
- 遇到console.log(4),直接打印4
- 遇到定時器,它是新的宏任務,先放著不執行
- 遇到async1,執行它先打印1;然后遇到await async2(),await是等待的意思,后面的代碼要先等它執行,執行它打印3;然后阻塞下面的代碼需要加入微任務隊列記為微1,然后跳出去執行同步代碼
- 然后遇到new Promise,直接執行,打印6
- 遇到promise.then屬于微任務,放入微任務列表記為微2
- 遇到console.log(8),直接打印8
- 然后去執行微任務,微任務隊列有微1、微2兩個任務,依次執行打印27
- 上一個宏任務所有事都完了,開始下一個宏任務,執行定時器,打印5
- 最后結果為:4 1 3 6 8 2 7 5
將上面的經典改造一下,用最終完全體來結束示例:
async function async1() { console.log(1); await async2(); setTimeout(() => { console.log(2); }, 0)}async function async2() { console.log(3); new Promise((resolve) => { console.log(9); resolve(); }).then(res => { console.log(10); })}console.log(4);setTimeout(() => { console.log(5);});async1();new Promise((resolve) => { console.log(6); resolve();}).then(() => { console.log(7);});console.log(8);分析過程,如下:
- 代碼開始執行
- 遇到console.log(4),直接打印4
- 遇到第一個setTimeout定時器,它是新的宏任務,先放著不執行
- 遇到async1,執行它先打印1;然后遇到await async2(),執行打印3;然后遇到new Promise直接打印9;resolve()執行,promise.then放入微任務隊列微1;await阻塞的代碼也需要加入微任務隊列記為微2;然后跳出去執行同步代碼
- 遇到new Promise,直接執行,打印6
- 遇到promise.then屬于微任務,放入微任務列表記為微3
- 遇到console.log(8),直接打印8
- 然后去執行微任務,微任務隊列有微1、微2、微3三個任務,依次執行,先打印10,遇到微2是setTimeout定時器,所以放入宏任務隊列;執行微3,打印7
- 上一個宏任務所有事都完了,開始下一個宏任務,執行定時器,打印5;沒有微任務,結束第二輪宏任務
- 開始第二個宏任務,執行打印2,到此全部結束
- 最后結果為:4 1 3 9 6 8 10 7 5 2
到這里,JS事件循環機制示例相關的題和分析過程就介紹完了,想必通過上面的介紹你一定能對JS的事件循環機制有更深的理解,相信以后遇見其他類似的面試題也能迎刃而解了。沖沖沖!→
最后,xdm看文至此,點個贊再走哦,3Q^_^
往期精彩文章后語
- 炒冷飯系列1:一道字節面試題引出的this指向問題
- 最值得擁有且最詳細的Git使用教程
- GitHub Copilot體驗:你的人工智能結對程序員來啦
- 快來看看我們團隊是如何制定前端開發規范的?
- 前端開發中常見的瀏覽器兼容性問題及解決方案大匯總
- 展望2022年,前端技術將有哪些新趨勢?
伙伴們,如果覺得本文對你有些許幫助,點個或者?個關注再走唄^_^ 。另外如果本文章有問題或有不理解的部分,歡迎大家在評論區評論指出,我們一起討論共勉。
歡迎分享轉載→http://m.avcorse.com/read-553694.html
下一篇:紅娘是哪一部作品中的人物
Copyright ? 2024 有趣生活 All Rights Reserve吉ICP備19000289號-5 TXT地圖HTML地圖XML地圖