摘要:但是事件循環(huán)一般會在主線程中任務執(zhí)行完成之后執(zhí)行。所以,上面函數(shù)的調用棧過程如下總結的事件循環(huán)部分,內容應該算是全部闡述完全了。
前言
還記得那些年面試官問你的定時器的原理嗎?還有呢?Promise的原理呢?原理、原理、原理,問的我們懷疑人生。
為了下次不再懵逼,今天,我們來了解一下Event Loop的概念。我們的初衷是真正的了解和掌握它,了解整體JavaScript的運行機制。至少,我們在看完文章的時候,不會讓我們在懷疑人生了。如果你喜歡我的文章,歡迎評論,歡迎Star~。歡迎關注我的github博客
正文聊這一個話題,我們必須從JavaScript本身聊起。
為什么瀏覽器選擇了我眾所周知,JavaScript是一門單線程語言。為什么會在瀏覽器端開發(fā)一門這樣子的單線程原因呢?原因就是——簡單。在瀏覽器端,復雜的UI環(huán)境會限制多線程語言的開發(fā)。例如,一個線程在操作一個DOM元素時,另一個線程需要去刪除DOM元素,這個之間就需要進行狀態(tài)的同步,何況前端可能不止操作這么一個DOM元素。所以,為了避免在開發(fā)過程中,去進行復雜的同步,選用單線程語言進行開發(fā)是最好的解決方案。
但是,單線程語言也會有問題——同步阻塞。舉個例子,cpu在執(zhí)行程序的過程中,執(zhí)行到IO操作,它會發(fā)送IO請求,之后等待數(shù)據(jù)返回,待數(shù)據(jù)返回之后才會繼續(xù)執(zhí)行后面的任務,如圖。
這種問題在瀏覽器中,或許是致命的。因此,JavaScript在執(zhí)行過程中,將任務分為同步任務和異步任務。
同步和異步的我首先,我們來了解一下主線程的執(zhí)行棧(call stack)。一個線程對應著一個執(zhí)行棧,所有同步任務都會被放入到執(zhí)行棧中執(zhí)行,例如,我們有下面這樣子的一個程序:
function fun1(){ return "hello hip-hop"; } function fun2(){ return fun1(); } function fun3(){ console.log(fun2()); } fun3(); //"hello hip-hop"
調用棧執(zhí)行過程:
或者我們使用另一種方法來論證一下:我們將fun1改成throw new Error("hello hip-hop")
function fun1(){ throw new Error("hello hip-hop"); } function fun2(){ return fun1(); } function fun3(){ console.log(fun2()); } fun3();
每次調用過程中,會有函數(shù)的出棧和入棧。這就是同步任務執(zhí)行的過程,一個任務執(zhí)行完成之后,執(zhí)行下一個任務。那么,我們在來看一下另外一個例子:
console.log("first"); setTimeout(() => { console.log("second"); }, 500); console.log("three");
我們依舊來看一下這個程序的調用棧的執(zhí)行順序:
從圖中,我們可以看到setTimeout執(zhí)行完成之后,已經(jīng)出棧了,但是后來的console.log("second")又是如何入棧的呢?
其實,在主線程之外,還有一個任務隊列。在任務隊列中,會存放著異步任務,只有當指定事件發(fā)生之后,異步任務才會被放到主線程中執(zhí)行。
其實,任務隊列中是一個事件隊列,那setTimeout舉例來說,當主線程執(zhí)行setTimeout的時候,會創(chuàng)建一個定時器,一旦定時器的時間達到了,就會將其內部的回調函數(shù),放入任務隊列中。然后,主線程在執(zhí)行的最后去循環(huán)任務隊列。而回調函數(shù),指的是主線程掛起的代碼,異步任務必須有回調函數(shù)才能執(zhí)行接下來的操作。如圖:
事件循環(huán)其實,上圖中,我們可以看到一個循環(huán),如圖:
這是一個死循環(huán),無論哪種情況都是閉環(huán)。其實,這就是事件循環(huán)。事件循環(huán)會不斷地去檢測任務隊列中是否還有已觸發(fā)時間的任務,如果有的話,就放入主線程中執(zhí)行。但是事件循環(huán)一般會在主線程中任務執(zhí)行完成之后執(zhí)行。我們可以來看一下另一幅圖片:
這幅圖片中,我們可以看到完整的執(zhí)行流程,其中涉及到的異步事件有DOM事件、ajax請求和setTimeout。所以,整體的執(zhí)行流程是這樣子的:
(1)、所有同步任務會在主線程的調用棧中執(zhí)行。
(2)、在主線程之外,還有一個任務隊列,一旦指定事件發(fā)生之后,異步任務就會被放入任務隊列中
(3)、當主線程執(zhí)行完調用棧中的同步任務時,會遍歷任務隊列,將任務隊列中的任務放入主線程中執(zhí)行。而之后事件循環(huán)一直會去遍歷任務隊列,一旦有任務放入就會放入主線程中執(zhí)行。
這樣,我們就已經(jīng)初步了解了同步和異步之間的實現(xiàn),以及瀏覽器中的事件循環(huán)機制。
任務隊列的不同自從ES6標準出來之后,Promise就被開發(fā)者關注到了。Promise是個很有意思的家伙,詳細講解的話,篇幅會太長。我們只關注它異步方面的任務隊列。
首先,我們來看一段程序:
setTimeout(() => { console.log(1); }, 0); Promise.resolve().then(() => { console.log(2); }).then(() => { console.log(3); }); console.log(4); // 4 2 3 1
你會對它的輸出感到疑惑嗎?相信經(jīng)歷過面試的你一定會?還是兩個字——懵逼
其實,這個執(zhí)行順序和任務隊列的種類有關系。我們一般一直稱呼地任務隊列(task queue),其實指的是Macrotasks。而Promise執(zhí)行后會被放到Microtasks中。
Macrotasks => 一般會將dom事件、ajax事件和setTimeout放入到這個隊列中。
Microtasks => 一般會將Promise、process.nextTicks、MutationObserver放入這個隊列中。
在執(zhí)行事件循環(huán)時,主線程會首先遍歷Microtasks,然后將隊列中的異步任務抽取出來執(zhí)行,直至抽空整個隊列,才會去執(zhí)行Macrotasks的隊列中的異步任務。
所以,上面函數(shù)的調用棧過程如下:
總結js的事件循環(huán)部分,內容應該算是全部闡述完全了。希望對看的你,會有收獲。
如果你對我寫的有疑問,可以評論,如我寫的有錯誤,歡迎指正。你喜歡我的博客,請給我關注Star~呦
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/91768.html
摘要:事件處理器,則是當指定事件觸發(fā)時,執(zhí)行的一段代碼。事件循環(huán)以一個無限循環(huán)的形式啟動,存在于二進制文件里函數(shù)的最后,當沒有更多可被執(zhí)行的事件處理器時,它就退出。 前言 如果你了解過Node.js,那么你一定聽說過事件循環(huán)。你一定想知道它為什么那么特殊,并且為什么你需要關注它?此時此刻的你,可能已經(jīng)寫過許多基于Express.js的后端代碼,但沒有接觸到任何的循環(huán)。 在下文中,我們會先在一...
摘要:檢索新的事件執(zhí)行與相關的回調幾乎所有,除了由定時器調度的一些和將在適當?shù)臅r候在這里阻塞。在事件循環(huán)的每次運行之間,檢查它是否在等待任何異步或定時器,如果沒有,則徹底關閉。 Node.js事件循環(huán)、定時器和process.nextTick() 什么是事件循環(huán)? 事件循環(huán)允許Node.js執(zhí)行非阻塞I/O操作 — 盡管JavaScript是單線程的 — 通過盡可能將操作卸載到系統(tǒng)內核。 ...
摘要:事件循環(huán)的順序,決定代碼執(zhí)行的順序。輸出第二輪事件循環(huán)正式結束三第三輪事件循環(huán)第三輪事件循環(huán)從宏任務開始。記為遇到,立即執(zhí)行回調函數(shù)放入中注冊,然后被分發(fā)到微任務事件隊列中。 1、為什么要有事件循環(huán)? 因為js是單線程的,事件循環(huán)是js的執(zhí)行機制,也是js實現(xiàn)異步的一種方法。 既然js是單線程,那就像只有一個窗口的銀行,客戶需要排隊一個一個辦理業(yè)務,同理js任務也要一個一個順序執(zhí)行。如...
摘要:異步請求線程在在連接后是通過瀏覽器新開一個線程請求將檢測到狀態(tài)變更時,如果設置有回調函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個回調再放入事件循環(huán)隊列中。 基礎:瀏覽器 -- 多進程,每個tab頁獨立一個瀏覽器渲染進程(瀏覽器內核) 每個瀏覽器渲染進程是多線程的,主要包括:GUI渲染線程 JS引擎線程 也稱為JS內核,負責處理Javascript腳本程序。(例如V8引擎) JS引擎線程負...
摘要:的一大特點就是單線程,而這個線程中擁有唯一的一個事件循環(huán)。事件循環(huán)基本概念代碼的執(zhí)行過程中,除了依靠函數(shù)調用棧來搞定函數(shù)的執(zhí)行順序外,還依靠任務隊列來搞定另外一些代碼的執(zhí)行。之后全局上下文進入函數(shù)調用棧。 JavaScript的一大特點就是單線程,而這個線程中擁有唯一的一個事件循環(huán)。 事件循環(huán)基本概念 JavaScript代碼的執(zhí)行過程中,除了依靠函數(shù)調用棧來搞定函數(shù)的執(zhí)行順序外,還...
摘要:輪詢投票處理下一次處理的新事件立即設置運行通過注冊的所有回調關閉執(zhí)行所有的回調工作處理延遲此度量標準測量線程池處理異步任務需要多長時間。高工作時間處理延遲表明繁忙耗盡的線程池。 原文=> What you should know to really understand the Node.js Event Loop Node.js 是一個基于事件的平臺。這就意味著在Node中發(fā)生的所...
閱讀 2434·2021-11-23 10:04
閱讀 1507·2021-09-02 15:21
閱讀 899·2019-08-30 15:44
閱讀 1070·2019-08-30 10:48
閱讀 716·2019-08-29 17:21
閱讀 3563·2019-08-29 13:13
閱讀 1991·2019-08-23 17:17
閱讀 1795·2019-08-23 17:04