摘要:二瀏覽器端在講解事件循環之前先談談中同步代碼異步代碼的執行流程。三端我自己認為的事件循環和瀏覽器端還是有點區別的,它的事件循環依靠引擎。四總結本篇主要介紹了瀏覽器和對于事件循環機制實現,由于能力水平有限,其中可能有誤之處歡迎指出。
一、前言
前幾天聽公司一個公司三年的前端說“今天又學到了一個知識點-微任務、宏任務”,我問他這是什么東西,由于在吃飯他淺淺的說了下,當時沒太理解就私下學習整理一番,由于談微任務、宏任務必談到事件循環,于是就有了這篇博客。
在談到事件循環機制之前我們需要知道一些基礎知識就是:
js是單線程的
js一開始是作為腳本語言運行在客戶端
其實js是單線程在它作為腳本語言操作dom的時候就決定了。那么此時就有一個性能問題,那么js在瀏覽器端是如何處理這個問題的呢?同時,js在后臺Node中又是如何解決的呢?這就是本篇需要介紹的事件循環機制,這里我將分別以瀏覽器和Node兩個方面來分析。
二、瀏覽器端在講解事件循環之前先談談js中同步代碼、異步代碼的執行流程。
2.1、js同步代碼執行過程js引擎在執行通過代碼的過程中,會安裝順序依次存儲到一個地方去,這個地方就叫做執行棧,當我們調用一個方法的時候,js會生成一個和這個方法相對應的上下文(context)。這個執行環境中存在著這個方法的私有作用域,上層作用域的指向,方法的參數,這個作用域中定義的變量以及這個作用域的this對象。
function a() { console.log("method a execute..."); } function b() { a(); } function c() { b(); } c();
以上面例子分析:js在執行的時候會有一個全局上下文,我們這里就稱為GContext,下面分析步驟
調用c(),c入棧,此時棧中內容為:GContext->c-contextC
接著調用b(),b入棧,此時棧中內容為:GContext->c->contextC->b->contextB
接著調用a(),a入棧,此時棧中內容為:GContext->c->contextC->b->contextB-c->contextC
a執行完,a出棧,此時棧中內容為:GContext->c->contextC->b->contextB
b執行完,b出棧,此時棧中內容為:GContext->c->contextC
c執行完,b出棧,此時棧中內容為:GContext
全部執行完,釋放資源
ok,上面是同步代碼的執行,上面會涉及到兩個核心概念:執行整個代碼的線程我們稱之為主線程,存放方法執行的地方我們稱之為執行棧.
2.2、js異步代碼執行過程上面說完了同步過程,那這里來談談異步的過程。js引擎在遇到一個異步事件,不會一直等待返回結果而是將它掛起。當異步任務執行完之后會將結果加入到和執行棧中不同的任務隊列當中,注意的是:此時放入隊列不會立即執行其回調,而是當主線程執行完執行棧中所有的任務之后再去隊列中查找是否有任務,如果有則取出排在第一位的事件然后將回調放入執行棧并執行其代碼。如此反復就構成了事件循環。
這里同樣有一個核心概念:任務隊列
2.3、微任務、宏任務上面提到js執行異步方法的時候會將其返回結果放到隊列中,這是比較籠統的,具體來說,js會根據任務的類型將其放入不同的隊列,任務類型有兩種:微任務、宏任務。那么其對應的哪些是微任務、哪些是宏任務呢?
微任務:Promise、process.nextTick()、整體代碼script、Object.observer、MutationObserver
宏任務:setTimeout()、setInterval()
瀏覽器在執行的時候,先從宏任務隊列中取出一個宏任務執行宏,然后在執行該宏任務下的所有的微任務,這是一個循環;然后再取出并執行下一個宏任務,再執行所有的微任務,這是第二個循環,以此類推.
注意:整個javascript代碼是第一個宏任務
const process = require("process") setTimeout(function () {// 分發宏任務到EventQueue console.log("1"); }, 0); setTimeout(() => { console.log("11"); }, 0); setTimeout(() => { console.log("111"); }, 0); new Promise(function (resolve) { console.log("2"); resolve(); }).then(function () {// 發送微任務 console.log("3"); });
// 輸出 2 3 1 11 1112.4、小結
在瀏覽器端,在我們執行一片script的時候,當遇到同步代碼,依次進入執行棧,遇到異步代碼,將其掛起,繼續執行其它方法,當異步方法執行完之后根據任務類型進入到任務隊列,在執行棧執行完,主線程空閑下來了之后會到任務隊列中取任務回調并執行。
三、Node端我自己認為Node的事件循環和瀏覽器端還是有點區別的,它的事件循環依靠libuv引擎。
該圖來自官網,這里展示了在node的事件循環的6個階段。
timers:該階段執行定時器的回調,如setTimeout() 和 setInterval()。
I/O callbacks:該階段執行除了close事件,定時器和setImmediate()的回調外的所有回調
idle, prepare:內部使用
poll:等待新的I/O事件,node在一些特殊情況下會阻塞在這里
check: setImmediate()的回調會在這個階段執行
close callbacks: 例如socket.on("close", ...)這種close事件的回調
對于我們來說我們更關注 timer、poll、check這三個階段即可。
poll 階段有兩個主要的功能:
處理poll隊列(poll quenue)的事件(callback);
執行timers的callback,當到達timers指定的時間時;
poll 階段的邏輯
如果event loop進入了 poll階段,且代碼未設定timer,將會發生下面情況:
a、如果poll queue不為空,event loop將同步的執行queue里的callback,直至queue為空,或執行的callback到達系統上限;
b、如果poll queue為空,將會發生下面情況:
* 如果代碼已經被setImmediate()設定了callback, event loop將結束poll階段進入check階段,并執行check階段的queue (check階段的queue是 setImmediate設定的) * 如果代碼沒有設定setImmediate(callback),event loop將阻塞在該階段等待callbacks加入poll queue;
如果event loop進入了 poll階段,且代碼設定了timer:
如果poll queue進入空狀態時(即poll 階段為空閑狀態),event loop將檢查timers,
如果有1個或多個timers時間時間已經到達,event loop將按循環順序進入 timers 階段,并執行timer queue
3.1、setTimeout、setImmediate這兩個函數的功能還是類似的,不同的是他們處于EventLoop的不同階段:timer、check。
setImmediate(()=>console.log("setInterval")); setTimeout(() => {console.log("setTimeout")},0);
上面兩行代碼會輸出順序是什么呢?其實兩種可能都有.
1.當setTimeout的0ms并不能做到絕對0ms,如果已經過了timer階段,那么此時setTimeout就會在下一次循環中執行,也就是說先setInterval、再setTimeout。
2.第二種可能就是正常流程了,先timer、再check
如果上面的代碼再一個IO操作作呢?如:
require("fs").readFile(__filename,()=>{ setImmediate(()=>console.log("setInterval")); setTimeout(() => {console.log("setTimeout")}); })
此時只可能出現一種情況,先setInterval、再setTimeout,因為在io中已經執行過了timer(readFile時處于IO callback)。
下面一起來看如下代碼:
setTimeout(() => { console.log("timer1") Promise.resolve().then(() => console.log("promise1")); process.nextTick(() => console.log("nextTick1")) }, 0); setTimeout(() => { console.log("timer2") Promise.resolve().then(() => console.log("promise2")); process.nextTick(() => console.log("nextTick2")) }, 0);
按照我的理解,它的輸出應該是如下:先timer、然后切換階段的時候執行微任務.
// 情況1 timer1 timer2 nextTick1 nextTick2 promise1 promise2
可是并不是,它的輸出一直是:
// 情況2 timer1 nextTick1 promise1 timer2 nextTick2 promise2
后臺晚上查資料因為Node11對EventLoop作了修改,為了和瀏覽器兼容。于是呼我切換到10.8.0,發現上面兩種情況都有(情況1比例大于情況2)。這點暫時還未查明什么原因。
3.2、小結node中的6個階段每個階段執行完都會伴隨著執行微任務,同個MicroTask隊列下process.tick()會優于Promise。
四 總結本篇主要介紹了瀏覽器和Node對于事件循環機制實現,由于能力水平有限,其中可能有誤之處歡迎指出。
歡迎關注公眾號:
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109612.html
摘要:的單線程,與它的用途有關。特點的顯著特點異步機制事件驅動。隊列的讀取輪詢線程,事件的消費者,的主角。它將不同的任務分配給不同的線程,形成一個事件循環,以異步的方式將任務的執行結果返回給引擎。 這兩天跟同事同事討論遇到的一個問題,js中的event loop,引出了chrome與node中運行具有setTimeout和Promise的程序時候執行結果不一樣的問題,從而引出了Nodejs的...
摘要:單線程的話,如果我們做一些的操作比如說這是一個耗時的操所那么在這將近一秒內,線程就會被阻塞,無法繼續執行下面的任務。事件循環的主要機制就是任務隊列機制一個事件循環有一個或者多個任務隊列。 瀏覽器中的事件循環機制 網上一搜事件循環, 很多文章標題的前面會加上 JavaScript, 但是我覺得事件循環機制跟 JavaScript 沒什么關系, JavaScript 只是一門解釋型語言, ...
摘要:如果當前沒有事件也沒有定時器事件,則返回。相關資料關于的架構及設計思路的事件討論了使用線程池異步運行代碼。下一篇初窺事件機制的實現二中定時器的實現 在瀏覽器中,事件作為一個極為重要的機制,給予JavaScript響應用戶操作與DOM變化的能力;在Node.js中,事件驅動模型則是其高并發能力的基礎。 學習JavaScript也需要了解它的運行平臺,為了更好的理解JavaScript的事...
摘要:的事件循環一個線程有唯一的一個事件循環。索引就是指否還有需要執行的事件,是否還有請求,關閉事件循環的請求等等。先來看一下定義的定義是在事件循環的下一個階段之前執行對應的回調。雖然是這樣定義的,但是它并不是為了在事件循環的每個階段去執行的。 Node中的事件循環 如果對前端瀏覽器的時間循環不太清楚,請看這篇文章。那么node中的事件循環是什么樣子呢?其實官方文檔有很清楚的解釋,本文先從n...
摘要:瀏覽器中與中事件循環與執行機制不同,不可混為一談。瀏覽器環境執行為單線程不考慮,所有代碼皆在執行線程調用棧完成執行。參考文章強烈推薦不要混淆和瀏覽器中的強烈推薦中的模塊強烈推薦理解事件循環一淺析定時器詳解 注意 在 node 11 版本中,node 下 Event Loop 已經與瀏覽器趨于相同。在 node 11 版本中,node 下 Event Loop 已經與瀏覽器趨于相同。在 ...
閱讀 2284·2023-04-25 16:42
閱讀 1198·2021-11-22 14:45
閱讀 2329·2021-10-19 13:10
閱讀 2821·2021-09-29 09:34
閱讀 3398·2021-09-23 11:21
閱讀 2094·2021-08-12 13:25
閱讀 2176·2021-07-30 15:15
閱讀 3488·2019-08-30 15:54