成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

面試率超高的JS事件循環,看這篇就夠了

開發 前端
事件循環是 JavaScript 中一個非常重要的概念,下面就來看看瀏覽器和 Node.js 中的事件循環的原理,以及兩者之間的差異!

大家好,我是 CUGGZ。

事件循環是 JavaScript 中一個非常重要的概念,下面就來看看瀏覽器和 Node.js 中的事件循環的原理,以及兩者之間的差異!

1、異步執行原理

(1)單線程的JavaScript

我們知道,JavaScript是一種單線程語言,它主要用來與用戶互動,以及操作DOM。

JavaScript 有同步和異步的概念,這就解決了代碼阻塞的問題:

  • 同步:如果在一個函數返回的時候,調用者就能夠得到預期結果,那么這個函數就是同步的。
  • 異步:如果在函數返回的時候,調用者還不能夠得到預期結果,而是需要在將來通過一定的手段得到,那么這個函數就是異步的。

那單線程有什么好處呢?

  • 在 JS 運行的時候可能會阻止 UI 渲染,這說明了兩個線程是互斥的。這是因為 JS 可以修改 DOM,如果在 JS 執行的時候 UI 線程還在工作,就可能導致不能安全的渲染 UI。
  • 得益于 JS 是單線程運行的,可以達到節省內存,節約上下文切換時間的好處。

(2)多線程的瀏覽器

JS 是單線程的,在同一個時間只能做一件事情,那為什么瀏覽器可以同時執行異步任務呢?

這是因為瀏覽器是多線程的,當 JS 需要執行異步任務時,瀏覽器會另外啟動一個線程去執行該任務。也就是說,JavaScript是單線程的指的是執行JavaScript代碼的線程只有一個,是瀏覽器提供的JavaScript引擎線程(主線程)。除此之外,瀏覽器中還有定時器線程、 HTTP 請求線程等線程,這些線程主要不是來執行 JS 代碼的。

比如主線程中需要發送數據請求,就會把這個任務交給異步 HTTP 請求線程去執行,等請求數據返回之后,再將 callback 里需要執行的 JS 回調交給 JS 引擎線程去執行。也就是說,瀏覽器才是真正執行發送請求這個任務的角色,而 JS 只是負責執行最后的回調處理。所以這里的異步不是 JS 自身實現的,而是瀏覽器為其提供的能力。

下圖是Chrome瀏覽器的架構圖:

圖片

可以看到,Chrome不僅擁有多個進程,還有多個線程。以渲染進程為例,就包含GUI渲染線程、JS引擎線程、事件觸發線程、定時器觸發線程、異步HTTP請求線程。這些線程為 JS 在瀏覽器中完成異步任務提供了基礎。

2、瀏覽器事件循環

JavaScript的任務分為兩種同步和異步:

  • 同步任務: 在主線程上排隊執行的任務,只有一個任務執行完畢,才能執行下一個任務,
  • 異步任務: 不進入主線程,而是放在任務隊列中,若有多個異步任務則需要在任務隊列中排隊等待,任務隊列類似于緩沖區,任務下一步會被移到執行棧然后主線程執行調用棧的任務。

上面提到了任務隊列和執行棧,下面就先來看看這兩個概念。

(1)執行棧與任務隊列

執行棧:從名字可以看出,執行棧使用到的是數據結構中的棧結構, 它是一個存儲函數調用的棧結構,遵循先進后出的原則。它主要負責跟蹤所有要執行的代碼。 每當一個函數執行完成時,就會從堆棧中彈出(pop)該執行完成函數;如果有代碼需要進去執行的話,就進行 push 操作。以下圖為例:

圖片

當執行這段代碼時,首先會執行一個 main 函數,然后執行我們的代碼。根據先進后出的原則,后執行的函數會先彈出棧,在圖中也可以發現,foo 函數后執行,當執行完畢后就從棧中彈出了。

JavaScript在按順序執行執行棧中的方法時,每次執行一個方法,都會為它生成獨有的執行環境(上下文),當這個方法執行完成后,就會銷毀當前的執行環境,并從棧中彈出此方法,然后繼續執行下一個方法。

任務隊列: 從名字中可以看出,任務隊列使用到的是數據結構中的隊列結構,它用來保存異步任務,遵循先進先出的原則。它主要負責將新的任務發送到隊列中進行處理

JavaScript在執行代碼時,會將同步的代碼按照順序排在執行棧中,然后依次執行里面的函數。當遇到異步任務時,就將其放入任務隊列中,等待當前執行棧所有同步代碼執行完成之后,就會從異步任務隊列中取出已完成的異步任務的回調并將其放入執行棧中繼續執行,如此循環往復,直到執行完所有任務。

JavaScript任務的執行順序如下:

圖片

在事件驅動的模式下,至少包含了一個執行循環來檢測任務隊列中是否有新任務。通過不斷循環,去取出異步任務的回調來執行,這個過程就是事件循環,每一次循環就是一個事件周期。

(2)宏任務和微任務

任務隊列其實不止一種,根據任務種類的不同,可以分為微任務(micro task)隊列宏任務(macro task)隊列。常見的任務如下:

  • 宏任務: script( 整體代碼)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 環境)。
  • 微任務: Promise、MutaionObserver、process.nextTick(Node.js 環境)。

任務隊列執行順序如下:

圖片

可以看到,Eventloop 在處理宏任務和微任務的邏輯時的執行情況如下:

  1. JavaScript 引擎首先從宏任務隊列中取出第一個任務;
  2. 執行完畢后,再將微任務中的所有任務取出,按照順序分別全部執行(這里包括不僅指開始執行時隊列里的微任務),如果在這一步過程中產生新的微任務,也需要執行,也就是說在執行微任務過程中產生的新的微任務并不會推遲到下一個循環中執行,而是在當前的循環中繼續執行。
  3. 然后再從宏任務隊列中取下一個,執行完畢后,再次將 microtask queue 中的全部取出,循環往復,直到兩個 queue 中的任務都取完。

也是就是說,一次 Eventloop 循環會處理一個宏任務和所有這次循環中產生的微任務。

下面通過一個例子來體會事件循環:

console.log('同步代碼1');

setTimeout(() {
console.log('setTimeout')
}, 0)

new Promise((resolve) => {
console.log('同步代碼2')
resolve()
}).then(() {
console.log('promise.then')
})

console.log('同步代碼3');

代碼輸出結果如下:

"同步代碼1"
"同步代碼2"
"同步代碼3"
"promise.then"
"setTimeout"

那這段代碼執行過程是怎么的呢?

  1. 遇到第一個console,它是同步代碼,加入執行棧,執行并出棧,打印出"同步代碼1"。
  2. 遇到setTimeout,它是一個宏任務,加入宏任務隊列。
  3. 遇到new Promise 中的console,它是同步代碼,加入執行棧,執行并出棧,打印出"同步代碼2"。
  4. 遇到Promise then,它是一個微任務,加入微任務隊列。
  5. 遇到第三個console,它是同步代碼,加入執行棧,執行并出棧,打印出"同步代碼3"。
  6. 此時執行棧為空,去執行微任務隊列中所有任務,打印出"promise.then"。
  7. 執行完微任務隊列中的任務,就去執行宏任務隊列中的一個任務,打印出"setTimeout"。

從上面的宏任務和微任務的工作流程中,可以得出以下結論:

  • 微任務和宏任務是綁定的,每個宏任務在執行時,會創建自己的微任務隊列。
  • 微任務的執行時長會影響當前宏任務的時長。比如一個宏任務在執行過程中,產生了 10 個微任務,執行每個微任務的時間是 10ms,那么執行這 10 個微任務的時間就是 100ms,也可以說這 10 個微任務讓宏任務的執行時間延長了 100ms。
  • 在一個宏任務中,分別創建一個用于回調的宏任務和微任務,無論什么情況下,微任務都早于宏任務執行(優先級更高)。

那么問題來了,為什么要將任務隊列分為微任務和宏任務呢,他們之間的本質區別是什么呢?

JavaScript在遇到異步任務時,會將此任務交給其他線程來執行(比如遇到setTimeout任務,會交給定時器觸發線程去執行,待計時結束,就會將定時器回調任務放入任務隊列等待主線程來取出執行),主線程會繼續執行后面的同步任務。

對于微任務,比如promise.then,當執行promise.then時,瀏覽器引擎不會將異步任務交給其他瀏覽器的線程去執行,而是將任務回調存在一個隊列中,當執行棧中的任務執行完之后,就去執行promise.then所在的微任務隊列。

所以,宏任務和微任務的本質區別如下:

  • 微任務:不需要特定的異步線程去執行,沒有明確的異步任務去執行,只有回調。
  • 宏任務:需要特定的異步線程去執行,有明確的異步任務去執行,有回調。

3、Node.js的事件循環

(1)事件循環的概念

對于Node.js的事件循環,官網的描述如下:

When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

翻譯一下就是:當Node.js啟動時,它會初始化一個事件循環,來處理輸入的腳本,這個腳本可能進行異步API的調用、調度計時器或調用process.nextTick(),然后開始處理事件循環。

JavaScript和Node.js是基于V8 引擎的,瀏覽器中包含的異步方式在 NodeJS 中也是一樣的。除此之外,Node.js中還有一些其他的異步形式:

  • 文件 I/O:異步加載本地文件。
  • setImmediate():與 setTimeout 設置 0ms 類似,在某些同步任務完成后立馬執行。
  • process.nextTick():在某些同步任務完成后立馬執行。
  • server.close、socket.on('close',...)等:關閉回調。

這些異步任務的執行就需要依靠Node.js的事件循環機制了。

Node.js 中的 Event Loop 和瀏覽器中的是完全不相同的東西。Node.js使用V8作為js的解析引擎,而I/O處理方面使用了自己設計的libuv,libuv是一個基于事件驅動的跨平臺抽象層,封裝了不同操作系統一些底層特性,對外提供統一的API,事件循環機制也是它里面的實現的,如下圖所示:

圖片

根據上圖,可以看到Node.js的運行機制如下:

  1. V8引擎負責解析JavaScript腳本。
  2. 解析后的代碼,調用Node API。
  3. libuv庫負責Node API的執行。它將不同的任務分配給不同的線程,形成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
  4. V8引擎將結果返回給用戶。

(2)事件循環的流程

其中libuv引擎中的事件循環分為 6 個階段,它們會按照順序反復運行。每當進入某一個階段的時候,都會從對應的回調隊列中取出函數去執行。當隊列為空或者執行的回調函數數量到達系統設定的閾值,就會進入下一階段。下面 是Eventloop 事件循環的流程:

圖片

整個流程分為六個階段,當這六個階段執行完一次之后,才可以算得上執行了一次 Eventloop 的循環過程。下面來看下這六個階段都做了哪些事:

  1. timers 階段:執行timer(setTimeout、setInterval)的回調,由 poll 階段控制。
  2. I/O callbacks 階段:主要執行系統級別的回調函數,比如 TCP 連接失敗的回調。
  3. idle, prepare 階段:僅Node.js內部使用,可以忽略。
  4. poll 階段:輪詢等待新的鏈接和請求等事件,執行 I/O 回調等。
  5. check 階段:執行 setImmediate() 的回調。
  6. close callbacks 階段:執行關閉請求的回調函數,比如socket.on('close', ...)。

注意:上面每個階段都會去執行完當前階段的任務隊列,然后繼續執行當前階段的微任務隊列,只有當前階段所有微任務都執行完了,才會進入下個階段,這里也是與瀏覽器中邏輯差異較大的地方。

其中,這里面比較重要的就是第四階段:poll,這一階段中,系統主要做兩件事:

  • 回到 timer 階段執行回調。
  • 執行 I/O 回調。

在進入該階段時如果沒有設定了 timer 的話,會出現以下情況:

(1)如果 poll 隊列不為空,會遍歷回調隊列并同步執行,直到隊列為空或者達到系統限制。

(2)如果 poll 隊列為空時,會出現以下情況:

  • 如果有 setImmediate 回調需要執行,poll 階段會停止并且進入到 check 階段執行回調。
  • 如果沒有 setImmediate 回調需要執行,會等待回調被加入到隊列中并立即執行回調,這里同樣會有個超時時間設置防止一直等待下去。

當設定了 timer 且 poll 隊列為空,則會判斷是否有 timer 超時,如果有的就會回到 timer 階段執行回調。

這一過程的具體執行流程如下圖所示:

圖片

(3)宏任務和微任務

Node.js事件循環的異步隊列也分為兩種:宏任務隊列和微任務隊列。

  • 常見的宏任務:setTimeout、setInterval、 setImmediate、script(整體代碼)、 I/O 操作等。
  • 常見的微任務:process.nextTick、new Promise().then(回調)等。

(4)process.nextTick()

上面提到了process.nextTick(),它是node中新引入的一個任務隊列,它會在上述各個階段結束時,在進入下一個階段之前立即執行。

Node.js官方文檔的解釋如下:

process.nextTick()is not technically part of the event loop. Instead, thenextTickQueuewill be processed after the current operation is completed, regardless of the current phase of the event loop. Here, an operation is defined as a transition from the underlying C/C++ handler, and handling the JavaScript that needs to be executed.

例如下面的代碼:

setTimeout(() {
console.log('timeout');
}, 0);

Promise.resolve().then(() {
console.error('promise')
})

process.nextTick(() {
console.error('nextTick')
})

輸出結果如下:

nextTick
promise
timeout

可以看到,process.nextTick()是優先于promise的回調執行。

(5)setImmediate 和 setTimeout

上面還提到了setImmediate 和 setTimeout,這兩者很相似,主要區別在于調用時機的不同:

  • setImmediate:在poll階段完成時執行,即check階段。
  • setTimeout:在poll階段為空閑時,且設定時間到達后執行,但它在timer階段執行。

例如下面的代碼:

setTimeout(() {
console.log('timeout');
}, 0);
setImmediate(() {
console.log('setImmediate');
});

輸出結果如下:

timeout
setImmediate

在上面代碼的執行過程中,第一輪循環后,分別將 setTimeout  和 setImmediate 加入了各自階段的任務隊列。第二輪循環首先進入timers 階段,執行定時器隊列回調,然后 pending callbacks和poll 階段沒有任務,因此進入check 階段執行 setImmediate 回調。所以最后輸出為timeout、setImmediate。###= 4. Node與瀏覽器事件循環的差異 Node.js與瀏覽器的事件循環的差異如下:

  • Node.js:microtask 在事件循環的各個階段之間執行。
  • 瀏覽器:microtask 在事件循環的 macrotask 執行完之后執行。?圖片 ?

Nodejs和瀏覽器的事件循環流程對比如下:

  1. 執行全局的 Script 代碼(與瀏覽器無差)。
  2. 把微任務隊列清空:注意,Node 清空微任務隊列的手法比較特別。在瀏覽器中,我們只有一個微任務隊列需要接受處理;但在 Node 中,有兩類微任務隊列:next-tick 隊列和其它隊列。其中這個 next-tick 隊列,專門用來收斂 process.nextTick 派發的異步任務。在清空隊列時,優先清空 next-tick 隊列中的任務,隨后才會清空其它微任務
  3. 開始執行 macro-task(宏任務)。注意,Node 執行宏任務的方式與瀏覽器不同:在瀏覽器中,我們每次出隊并執行一個宏任務;而在 Node 中,我們每次會嘗試清空當前階段對應宏任務隊列里的所有任務(除非達到系統限制)。
  4. 步驟3開始,會進入 3 -> 2 -> 3 -> 2…的循環。
責任編輯:姜華 來源: 前端充電寶
相關推薦

2023-06-11 23:59:59

2024-08-27 11:00:56

單例池緩存bean

2021-09-30 07:59:06

zookeeper一致性算法CAP

2019-08-16 09:41:56

UDP協議TCP

2021-05-07 07:52:51

Java并發編程

2022-03-29 08:23:56

項目數據SIEM

2022-05-27 08:18:00

HashMapHash哈希表

2021-12-13 10:43:45

HashMapJava集合容器

2023-12-07 09:07:58

2022-08-18 20:45:30

HTTP協議數據

2023-09-25 08:32:03

Redis數據結構

2021-09-10 13:06:45

HDFS底層Hadoop

2023-11-07 07:46:02

GatewayKubernetes

2023-10-04 00:32:01

數據結構Redis

2021-07-28 13:29:57

大數據PandasCSV

2017-03-30 22:41:55

虛擬化操作系統軟件

2023-11-03 08:53:15

StrconvGolang

2023-11-22 07:54:33

Xargs命令Linux

2021-10-21 06:52:17

ZooKeeper分布式配置

2018-09-26 11:02:46

微服務架構組件
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲欧美日韩在线 | 欧美日韩亚洲国产综合 | 欧美精品一区二区三区四区五区 | 99国产精品久久久久久久 | 日韩一区二区在线视频 | 综合久久av| 亚洲黄色国产 | 久久国产精品亚洲 | 奇米视频777 | 精品久久久久香蕉网 | 亚洲一区网站 | 精品福利在线 | 色伊人| 亚洲成人www | 亚洲精品日本 | 成人精品国产一区二区4080 | 亚洲麻豆 | 毛片一区二区三区 | 亚洲欧美日韩在线 | 久久国产欧美一区二区三区精品 | 国产四虎| 亚洲国产精品福利 | 欧美久久久久 | 国产二区视频 | 日韩欧美在线观看 | 瑟瑟视频在线看 | 中文字幕av一区 | 成人精品鲁一区一区二区 | 中文字幕第一页在线 | 一区二区三区四区av | 国产精品久久久久久久 | 欧美日韩视频网站 | 日韩欧美亚洲 | 国产精品区一区二 | 国产久| 91免费视频观看 | 国产精品日本一区二区在线播放 | 国产黄色在线观看 | 亚洲97| 亚洲一区导航 | 久久久久国产一区二区三区 |