摘要:的宿主最開始本身就是瀏覽器,處理用戶的交互事件。既然是單線程的,那就意味著任務(wù)需要排隊,只有前一個任務(wù)執(zhí)行完畢,下一個任務(wù)才能開始,于是就有了任務(wù)隊列。事件循環(huán)有兩種用于瀏覽上下文的事件循環(huán)和用于的事件循環(huán)。
最近看到Event Loop這個詞出現(xiàn)的頻率有點高,于是查閱各方資料在此記錄一下。
先不說概念,我們來看段代碼:
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");
復(fù)制這段代碼到控制臺,在Chrome會輸出如下結(jié)果
Why?
如果想弄清楚原因,就必須得弄清楚今天要提到的概念Event Loop。
運行時概念棧
函數(shù)調(diào)用形成了一個棧幀。
function foo(b) { var a = 10; return a + b + 11; } function bar(x) { var y = 3; return foo(x * y); } console.log(bar(7)); // 返回 42
當(dāng)調(diào)用 bar 時,創(chuàng)建了第一個幀 ,幀中包含了 bar 的參數(shù)和局部變量。當(dāng) bar 調(diào)用 foo 時,第二個幀就被創(chuàng)建,并被壓到第一個幀之上,幀中包含了 foo 的參數(shù)和局部變量。當(dāng) foo 返回時,最上層的幀就被彈出棧(剩下 bar 函數(shù)的調(diào)用幀 )。當(dāng) bar 返回的時候,棧就空了。
堆
對象被分配在一個堆中,即用以表示一大塊非結(jié)構(gòu)化的內(nèi)存區(qū)域。
隊列
一個 JavaScript 運行時包含了一個待處理的消息隊列。每一個消息都關(guān)聯(lián)著一個用以處理這個消息的函數(shù)。
在事件循環(huán)期間的某個時刻,運行時從最先進(jìn)入隊列的消息開始處理隊列中的消息。為此,這個消息會被移出隊列,并作為輸入?yún)?shù)調(diào)用與之關(guān)聯(lián)的函數(shù)。正如前面所提到的,調(diào)用一個函數(shù)總是會為其創(chuàng)造一個新的棧幀。
函數(shù)的處理會一直進(jìn)行到執(zhí)行棧再次為空為止;然后事件循環(huán)將會處理隊列中的下一個消息(如果還有的話)。
為什么JavaScript是單線程稍理解JavaScript的都知道JavaScript是單線程,即同一時間只能處理一件事情。JavaScript為什么不能是多線程呢,這樣就可以同時處理多件事情提高效率。
JavaScript的宿主最開始本身就是瀏覽器,處理用戶的交互事件。作為瀏覽器腳本,它只能一次做一件事情,假如用戶點擊一個按鈕的時候,需要刪除一個節(jié)點,而另一段代碼此時又要添加這個節(jié)點,那JavaScript該如何處理,以誰為準(zhǔn)?
所以JavaScript在創(chuàng)造之初就考慮到了這點,也決定了它只能是單線程,這是它的核心特征之一。
Event Loop
既然JavaScript是單線程的,那就意味著任務(wù)需要排隊,只有前一個任務(wù)執(zhí)行完畢,下一個任務(wù)才能開始,于是就有了任務(wù)隊列。如果一個任務(wù)耗時很長,下面的任務(wù)就得一直等著,明顯不太合理,那么能否先把耗時很久的任務(wù)先掛起來,先執(zhí)行后面的任務(wù),等IO設(shè)備返回的結(jié)果,再去執(zhí)行之前掛著的任務(wù)。
于是任務(wù)就可以分兩種:同步任務(wù)和異步任務(wù)
同步任務(wù)指的是,在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù);異步任務(wù)指的是,不進(jìn)入主線程、而進(jìn)入"任務(wù)隊列"(task queue)的任務(wù),只有"任務(wù)隊列"通知主線程,某個異步任務(wù)可以執(zhí)行了,該任務(wù)才會進(jìn)入主線程執(zhí)行。
(1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)。
(2)主線程之外,還存在一個"任務(wù)隊列"(task queue)。只要異步任務(wù)有了運行結(jié)果,就在"任務(wù)隊列"之中放置一個事件。
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會讀取"任務(wù)隊列",看看里面有哪些事件。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開始執(zhí)行。
(4)主線程不斷重復(fù)上面的第三步。
異步任務(wù)指的是異步的代碼加入到任務(wù)隊列中,等待主線程通知執(zhí)行Event Loop
主線程從"任務(wù)隊列"中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運行機(jī)制又稱為Event Loop!
Event loop:客戶端必須使用本章節(jié)中所描述的事件循環(huán),來協(xié)調(diào)事件,用戶交互,腳本,呈現(xiàn),網(wǎng)絡(luò)等等。 事件循環(huán)有兩種:用于瀏覽上下文的事件循環(huán)和用于 worker 的事件循環(huán)。
任務(wù)隊列分為宏任務(wù)隊列(macro tasks) 和 微任務(wù)隊列(micro tasks)
如何判斷一段代碼是加入到宏任務(wù)隊列還是微任務(wù)隊列?
每個任務(wù)都由特殊任務(wù)源來定義。 來自同一個特殊任務(wù)源的所有任務(wù)都將發(fā)往特定事件循環(huán)。所以我們可以按照不同的來源進(jìn)行分類,不同來源的任務(wù)都對應(yīng)到不同的任務(wù)隊列中
(macro-task 宏任務(wù))來源:I/O, setTimeout + setInterval + setImmediate, UI renderder ···
(micro-task 微任務(wù))來源:Promise ,process.nextTick ,MutationObserver, Object.observe ···
Microtasks are usually scheduled for things that should happen straight after the currently executing script, such as reacting to a batch of actions, or to make something async without taking the penalty of a whole new task. The microtask queue is processed after callbacks as long as no other JavaScript is mid-execution, and at the end of each task. Any additional microtasks queued during microtasks are added to the end of the queue and also processed. Microtasks include mutation observer callbacks, and as in the above example, promise callbacks.
看下完整的執(zhí)行過程:
? 代碼開始執(zhí)行,JavaScript 引擎對所有的代碼進(jìn)行區(qū)分。
? 同步代碼被壓入棧中,異步代碼根據(jù)不同來源加入到宏任務(wù)隊列尾部,或者微任務(wù)隊列的尾部。
? 等待棧中的代碼被執(zhí)行完畢,此時通知任務(wù)隊列,執(zhí)行位于隊列首部的宏任務(wù)。
? 宏任務(wù)執(zhí)行完畢,開始執(zhí)行其關(guān)聯(lián)的微任務(wù)。
? 關(guān)聯(lián)的微任務(wù)執(zhí)行完畢,繼續(xù)執(zhí)行下一個宏任務(wù),直到任務(wù)隊列中所有宏任務(wù)被執(zhí)行完畢。
?執(zhí)行下一個任務(wù)隊列。
參考文檔:
并發(fā)模型與事件循環(huán)
JavaScript 運行機(jī)制詳解:再談Event Loop
什么是瀏覽器的事件循環(huán)(Event Loop)?
從 薛定諤的貓 聊到 Event loop
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103302.html
摘要:如果當(dāng)前沒有事件也沒有定時器事件,則返回。相關(guān)資料關(guān)于的架構(gòu)及設(shè)計思路的事件討論了使用線程池異步運行代碼。下一篇初窺事件機(jī)制的實現(xiàn)二中定時器的實現(xiàn) 在瀏覽器中,事件作為一個極為重要的機(jī)制,給予JavaScript響應(yīng)用戶操作與DOM變化的能力;在Node.js中,事件驅(qū)動模型則是其高并發(fā)能力的基礎(chǔ)。 學(xué)習(xí)JavaScript也需要了解它的運行平臺,為了更好的理解JavaScript的事...
摘要:心塞塞根據(jù)規(guī)范,事件循環(huán)是通過任務(wù)隊列的機(jī)制來進(jìn)行協(xié)調(diào)的。等便是任務(wù)源,而進(jìn)入任務(wù)隊列的是他們指定的具體執(zhí)行任務(wù)回調(diào)函數(shù)。然后當(dāng)前本輪的結(jié)束,主線程可以繼續(xù)取下一個執(zhí)行。 依然是:經(jīng)濟(jì)基礎(chǔ)決定上層建筑。 說明 首先,旨在搞清常用的同步異步執(zhí)行機(jī)制 其次,暫時不討論node.js的Event Loop執(zhí)行機(jī)制,以下關(guān)于瀏覽器的Event Loop執(zhí)行機(jī)制 最后,借鑒了很多前輩的研究文...
摘要:了解事件循環(huán)機(jī)制有助于理解的執(zhí)行過程,同時這也是面試常見題。那么這個回調(diào)函數(shù)將在何時由誰執(zhí)行呢已知是瀏覽器環(huán)境提供的,因此瀏覽器將對它進(jìn)行處理,瀏覽器會在本次事件完成,即計時結(jié)束后,將回調(diào)函數(shù)加入循環(huán)隊列中,然后等待被加入執(zhí)行棧執(zhí)行。 如果有人問JavaScript是什么,也許你會說它是一個單線程、非阻塞、異步、解釋型的腳本語言。那么作為一個單線程語言,它是怎么實現(xiàn)非阻塞、異步的?這就...
摘要:眾所周知,是,也就意味著在執(zhí)行的過程中,是,而這樣的特性,正是由一個叫的東西決定的有且僅有一個。無論從工程效率還是用戶體驗的角度來說,這都是不被允許的一件事情。五秒后,結(jié)束計時,將回調(diào)函數(shù)下放到中。至此,正式引出的概念。 前段時間在網(wǎng)上陸續(xù)看了很多關(guān)于 Event loop 的文章,看完也就混個眼熟,可能內(nèi)心深處對這種偏原理的知識有一些抵觸心情,看完后也都沒有去深入理解。最近在看 Vu...
摘要:瀏覽器推遲事件直到所有的腳本都處于狀態(tài)。解析器將處理執(zhí)行這個腳本。創(chuàng)建這個腳本的解析器的文檔有正在阻塞腳本執(zhí)行腳本元素為等待解析阻塞的腳本的狀態(tài),同一時刻只能有一個這樣的腳本存在。解析器將一個或多個字符轉(zhuǎn)換為表并處理,這個過程是一個典型的。 前言 本文主要對W3C規(guī)范中關(guān)于script標(biāo)簽和event loop相關(guān)的篇幅做了簡單的探討,針對一些必要的相關(guān)概念進(jìn)行了適當(dāng)?shù)臉?biāo)注和說明。雖然...