摘要:引擎是單線程的,如上圖中,它負責(zé)維護任務(wù)隊列,并通過的機制,按順序把任務(wù)放入棧中執(zhí)行。接下來,我們會細說圖中的棧和任務(wù)隊列。直到微任務(wù)隊列為空,執(zhí)行下一步。上一輪循環(huán)中有少數(shù)的會被延遲到這一輪的這一階段執(zhí)行。
概覽
我們經(jīng)常會聽到引擎和runtime,它們的區(qū)別是什么呢?
引擎:解釋并編譯代碼,讓它變成能交給機器運行的代碼(runnable commands)。
runtime:就是運行環(huán)境,它提供一些對外接口供Js調(diào)用,以跟外界打交道,比如,瀏覽器環(huán)境、Node.js環(huán)境。不同的runtime,會提供不同的接口,比如,在 Node.js 環(huán)境中,我們可以通過 require 來引入模塊;而在瀏覽器中,我們有 window、 DOM。
Js引擎是單線程的,如上圖中,它負責(zé)維護任務(wù)隊列,并通過 Event Loop 的機制,按順序把任務(wù)放入棧中執(zhí)行。而圖中的異步處理模塊,就是 runtime 提供的,擁有和Js引擎互不干擾的線程。接下來,我們會細說圖中的:棧和任務(wù)隊列。
棧現(xiàn)在,我們要運行下面這段代碼:
function bar() { console.log(1); } function foo() { console.log(2); far(); } setTimeout(() => { console.log(3) }); foo();
它在棧中的入棧、出棧過程,如下圖:
Js 中,有兩類任務(wù)隊列:宏任務(wù)隊列(macro tasks)和微任務(wù)隊列(micro tasks)。宏任務(wù)隊列可以有多個,微任務(wù)隊列只有一個。那么什么任務(wù),會分到哪個隊列呢?
宏任務(wù):script(全局任務(wù)), setTimeout, setInterval, setImmediate, I/O, UI rendering.
微任務(wù):process.nextTick, Promise, Object.observer, MutationObserver.
瀏覽器的 Event Loop瀏覽器的 Event Loop 遵循的是 HTML5 標(biāo)準(zhǔn),而 NodeJs 的 Event Loop 遵循的是 libuv。 區(qū)別較大,分開講。
我們上面講到,當(dāng)stack空的時候,就會從任務(wù)隊列中,取任務(wù)來執(zhí)行。瀏覽器這邊,共分3步:
取一個宏任務(wù)來執(zhí)行。執(zhí)行完畢后,下一步。
取一個微任務(wù)來執(zhí)行,執(zhí)行完畢后,再取一個微任務(wù)來執(zhí)行。直到微任務(wù)隊列為空,執(zhí)行下一步。
更新UI渲染。
Event Loop 會無限循環(huán)執(zhí)行上面3步,這就是Event Loop的主要控制邏輯。其中,第3步(更新UI渲染)會根據(jù)瀏覽器的邏輯,決定要不要馬上執(zhí)行更新。畢竟更新UI成本大,所以,一般都會比較長的時間間隔,執(zhí)行一次更新。
從執(zhí)行步驟來看,我們發(fā)現(xiàn)微任務(wù),受到了特殊待遇!我們代碼開始執(zhí)行都是從script(全局任務(wù))開始,所以,一旦我們的全局任務(wù)(屬于宏任務(wù))執(zhí)行完,就馬上執(zhí)行完整個微任務(wù)隊列??磦€例子:
console.log("script start"); // 微任務(wù) Promise.resolve().then(() => { console.log("p 1"); }); // 宏任務(wù) setTimeout(() => { console.log("setTimeout"); }, 0); var s = new Date(); while(new Date() - s < 50); // 阻塞50ms // 微任務(wù) Promise.resolve().then(() => { console.log("p 2"); }); console.log("script ent"); /*** output ***/ // one macro task script start script ent // all micro tasks p 1 p 2 // one macro task again setTimeout
上面之所以加50ms的阻塞,是因為 setTimeout 的 delayTime 最少是 4ms. 為了避免認為 setTimeout 是因為4ms的延遲而后面才被執(zhí)行的,我們加了50ms阻塞。
NodeJs 的 Event LoopNodeJs 的運行是這樣的:
初始化 Event Loop
執(zhí)行您的主代碼。這里同樣,遇到異步處理,就會分配給對應(yīng)的隊列。直到主代碼執(zhí)行完畢。
執(zhí)行主代碼中出現(xiàn)的所有微任務(wù):先執(zhí)行完所有nextTick(),然后在執(zhí)行其它所有微任務(wù)。
開始 Event Loop
NodeJs 的 Event Loop 分6個階段執(zhí)行:
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
以上的6個階段,具體處理的任務(wù)如下:
timers: 這個階段執(zhí)行setTimeout()和setInterval()設(shè)定的回調(diào)。
pending callbacks: 上一輪循環(huán)中有少數(shù)的 I/O callback 會被延遲到這一輪的這一階段執(zhí)行。
idle, prepare: 僅內(nèi)部使用。
poll: 執(zhí)行 I/O callback,在適當(dāng)?shù)臈l件下會阻塞在這個階段
check: 執(zhí)行setImmediate()設(shè)定的回調(diào)。
close callbacks: 執(zhí)行比如socket.on("close", ...)的回調(diào)。
每個階段執(zhí)行完畢后,都會執(zhí)行所有微任務(wù)(先 nextTick,后其它),然后再進入下一個階段。
LinksEvent loops
NodeJs 的 Event Loop 官方文檔
并發(fā)模型與事件循環(huán)
Philip Roberts: Help, I’m stuck in an event-loop.
Promise的隊列與setTimeout的隊列有何關(guān)聯(lián)?
JavaScript:徹底理解同步、異步和事件循環(huán)(Event Loop)
從event loop規(guī)范探究javaScript異步及瀏覽器更新渲染時機
JavaScript 運行機制詳解:再談Event Loop - 阮一峰的網(wǎng)絡(luò)日志
Tasks, microtasks, queues and schedules
WindowOrWorkerGlobalScope.setTimeout()
What is the difference between JavaScript Engine and JavaScript Runtime Environment
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/91862.html
摘要:主線程不斷重復(fù)上面的三步,此過程也就是常說的事件循環(huán)。所以主線程代碼執(zhí)行時間過長,會阻塞事件循環(huán)的執(zhí)行。參考資料這一次,徹底弄懂執(zhí)行機制任務(wù)隊列的順序機制事件循環(huán)搞懂異步事件輪詢與中的事件循環(huán) 1. 說明 讀過本文章后,您能知道: JavaScript代碼在瀏覽器中的執(zhí)行機制和事件循環(huán) 面試中經(jīng)常遇到的代碼輸出順序問題 首先通過一段代碼來驗證你是否了解代碼輸出順序,如果你不知道輸出...
摘要:單線程異步非阻塞然后,這又牽扯到了事件循環(huán)消息隊列,還有微任務(wù)宏任務(wù)這些。此步的位置不確定某個時刻后,定時器觸發(fā)線程通知事件觸發(fā)線程,事件觸發(fā)線程將回調(diào)函數(shù)加入消息隊列隊尾,等待引擎線程執(zhí)行。 前言 Philip Roberts 在演講 great talk at JSConf on the event loop 中說:要是用一句話來形容 JavaScript,我可能會這樣: Java...
摘要:當(dāng)函數(shù)結(jié)束,將會被從調(diào)用棧移出。事件循環(huán)事件循環(huán)的責(zé)任就是查看調(diào)用棧并確定調(diào)用棧是否為空。事件循環(huán)會再次檢查調(diào)用棧是否為空,如果為空的話,它會把事件回調(diào)壓入棧中,然后回調(diào)函數(shù)則被執(zhí)行。 寫在文章前 這篇文章是翻譯自Sukhjinder Arora的Understanding Asynchronous JavaScript。這篇文章描述了異步和同步JavaScript是如何在運行環(huán)境中,...
摘要:只要指定過回調(diào)函數(shù),這些事件發(fā)生時就會進入任務(wù)隊列,等待主線程讀取。三主線程從任務(wù)隊列中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運行機制又稱為事件循環(huán)。 一、任務(wù)隊列 同步任務(wù)與異步任務(wù)的由來 單線程就意味著,所有任務(wù)需要排隊,前一個任務(wù)結(jié)束,才會執(zhí)行后一個任務(wù)。如果前一個任務(wù)耗時很長,后一個任務(wù)就不得不一直等著。 如果排隊是因為計算量大,CPU忙不過來,倒也算了,但是很多時候C...
摘要:事件循環(huán)當(dāng)主線程中的任務(wù)執(zhí)行完畢后,會從任務(wù)隊列中獲取任務(wù)一個個的放在棧中執(zhí)行去執(zhí)行,這個過程是循環(huán)不斷的,所以整個的這種運行機制又稱為事件循環(huán)。 寫在前面 說起javascript(以下簡稱js)這門語言,相信大家已經(jīng)非常熟悉了,不管是前端開發(fā)還是后端開發(fā)幾乎無時無刻都要跟它打交道。雖說開發(fā)者每天幾乎都要操作js,但是你真的確定你掌握了js的運行機制嗎!下面我們就來聊聊這話題。 Ja...
摘要:異步任務(wù)必須指定回調(diào)函數(shù),當(dāng)異步任務(wù)從任務(wù)隊列回到執(zhí)行棧,回調(diào)函數(shù)就會執(zhí)行。事件循環(huán)主線程從任務(wù)隊列中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運行機制又稱為。事件循環(huán)事件循環(huán)是指主線程重復(fù)從消息隊列中取消息執(zhí)行的過程。 參考鏈接:這一次,徹底弄懂 JavaScript 執(zhí)行機制https://zhuanlan.zhihu.com/p/...從瀏覽器多進程到JS單線程,JS運行機制...
閱讀 2445·2021-11-23 10:09
閱讀 2965·2021-10-12 10:11
閱讀 2666·2021-09-29 09:35
閱讀 1405·2019-08-30 15:53
閱讀 2321·2019-08-30 11:15
閱讀 2980·2019-08-29 13:01
閱讀 2349·2019-08-28 18:15
閱讀 3441·2019-08-26 12:13