目錄
#JavaScript 中的非同步程式設計
JavaScript 是同步的
JavaScript 是阻塞的
JavaScript 是單執行緒的
等待 JavaScript
Node.js 運行時
Libuv
Node.js 運行時中的程式碼執行
同步代码执行
异步代码执行
Libuv 和异步操作
什麼是事件循環?
視覺化事件循環
事件循環是如何運作的?
結論
首頁 web前端 js教程 一起聊聊Node中的事件循環

一起聊聊Node中的事件循環

Apr 11, 2023 pm 07:08 PM
javascript 前端 node.js

事件循環是 Node.js 的基本組成部分,透過確保主執行緒不被阻塞來實現非同步編程,了解事件循環對建立高效應用程式至關重要。以下這篇文章就來帶大家深入了解Node中的事件循環 ,希望對大家有幫助!

一起聊聊Node中的事件循環

你已經使用 Node.js 一段時間了,建立了一些應用程序,嘗試了不同的模組,甚至對非同步程式設計感到很舒適。但有些事情一直困擾著你──事件循環(Event Loop)。

如果你像我一樣,花了無數個小時閱讀文檔和觀看視頻,試圖理解事件循環。但即使作為一個經驗豐富的開發者,在完全理解它如何運作方面也可能會遇到困難。這就是為什麼我準備了這份視覺指南,幫助您充分理解 Node.js 事件循環。請坐下來,拿杯咖啡,讓我們深入探索 Node.js 事件循環的世界。 【相關教學推薦:nodejs影片教學程式設計教學

#JavaScript 中的非同步程式設計

我們將從JavaScript 中非同步程式設計的複習開始。雖然 JavaScript 在 Web、行動和桌面應用程式中都有使用,但重要的是要記住,本質上,JavaScript 是一種同步、阻塞、單執行緒的語言。讓我們透過一個簡短的程式碼片段來理解這句話。

// index.js

function A() {
  console.log("A");
}

function B() {
  console.log("B");
}

A()
B()

// Logs A and then B
登入後複製

JavaScript 是同步的

如果我們有兩個將訊息記錄到控制台的函數,那麼程式碼會自上而下執行,每次只執行一行。在上述程式碼片段中,我們看到 A 在 B 之前被記錄。

JavaScript 是阻塞的

JavaScript 由於其同步性質而被封鎖。無論前一個進程需要多長時間,後續進程都不會啟動,直到前者完成為止。在程式碼片段中,如果函數 A 必須執行大量程式碼區塊,則 JavaScript 必須在沒有轉移到函數 B 的情況下完成該操作。即便這塊程式碼需要耗時 10 秒甚至 1 分鐘。

你可能已經在瀏覽器中遇到過這種情況。當 Web 應用程式在瀏覽器中運行並且執行一些密集的程式碼區塊而不返回控制權給瀏覽器時,瀏覽器可能會出現卡死的情況,這就是所謂的阻塞。瀏覽器被阻止繼續處理使用者輸入和執行其他任務,直到 Web 應用程式將處理器控制權歸還給瀏覽器。

JavaScript 是單執行緒的

執行緒就是你的 JavaScript 程式可以用來執行任務的進程(process)。每個執行緒一次只能執行一個任務。與其他支援多執行緒並且可以同時執行多個任務的語言不同,JavaScript 只有一個稱為主執行緒的執行緒執行程式碼。

等待 JavaScript

如你所想,這個 JavaScript 模型會帶來問題,因為我們必須等待資料被取得後才能繼續執行程式碼。這個等待可能需要幾秒鐘,在此期間我們無法運行任何其他程式碼。如果 JavaScript 在不等待的情況下繼續處理,就會出錯。我們需要在 JavaScript 中實作異步行為。我們進到 Node.js 看一下。

Node.js 運行時

一起聊聊Node中的事件循環

#Node.js 運行時是一個環境,​​你可以在不使用瀏覽器的情況下使用並執行JavaScript 程式。核心-Node 執行時,由三個主要元件組成。

  • 外部相依性 —— 例如 V8、libuv、crypto 等——是 Node.js 必要的功能
  • C 特性提供了檔案系統存取和網路等功能。
  • JavaScript 函式庫提供了函數和工具,方便使用 JavaScript 程式碼呼叫 C 特性。

雖然所有部分都很重要,但非同步程式設計在 Node.js 中的關鍵元件是 libuv。

Libuv

Libuv 是一個跨平台的開源函式庫,用 C 語言寫。在 Node.js 運行時中,它的作用是提供處理非同步操作的支援。讓我們來看看它是如何運作的。

Node.js 運行時中的程式碼執行

一起聊聊Node中的事件循環

#

让我们来概括一下代码在 Node 运行时中的执行方式。在执行代码时,位于图片左侧的 V8 引擎负责 JavaScript 代码的执行。该引擎包含一个内存堆(Memory heap)和一个调用栈(Call stack)。

每当声明变量或函数时,都会在堆上分配内存。执行代码时,函数就会被推入调用栈中。当函数返回时,它就从调用栈中弹出了。这是对栈数据结构的简单实现,最后添加的项是第一个被移除。在图片右侧,是负责处理异步方法的 libuv。

每当我们执行异步方法时,libuv 接管任务的执行。然后使用操作系统本地异步机制运行任务。如果本地机制不可用或不足,则利用其线程池来运行任务,并确保主线程不被阻塞。

同步代码执行

首先,让我们来看一下同步代码执行。以下代码由三个控制台日志语句组成,依次记录“First”,“Second”和“Third”。我们按照运行时执行顺序来查看代码。

// index.js
console.log("First");
console.log("Second");
console.log("Third");
登入後複製

以下是 Node 运行时执行同步代码的可视化展示。

一起聊聊Node中的事件循環

执行的主线程始终从全局作用域开始。全局函数(如果我们可以这样称呼它)被推入堆栈中。然后,在第 1 行,我们有一个控制台日志语句。这个函数被推入堆栈中。假设这个发生在 1 毫秒时,“First” 被记录在控制台上。然后,这个函数从堆栈中弹出。

执行到第 2 行时。假设到第 2 毫秒了,log 函数再次被推入堆栈中。“Second”被记录在控制台上,并弹出该函数。

最后,执行到第 3 行了。第 3 毫秒时,log 函数被推入堆栈,“Third”将记录在控制台上,并弹出该函数。此时已经没有代码要执行,全局也被弹出。

异步代码执行

接下来,让我们看一下异步代码执行。有以下代码片段:包含三个日志语句,但这次第二个日志语句传递给了fs.readFile() 作为回调函数。

一起聊聊Node中的事件循環

执行的主线程始终从全局作用域开始。全局函数被推入堆栈。然后执行到第 1 行,在第 1 毫秒时,“First”被记录在控制台中,并弹出该函数。然后执行移动到第 2 行,在第 2毫秒时,readFile 方法被推入堆栈。由于 readFile 是异步操作,因此它会转移(off-loaded)到 libuv。

JavaScript 从调用堆栈中弹出了 readFile 方法,因为就第 2 行的执行而言,它的工作已经完成了。在后台,libuv 开始在单独的线程上读取文件内容。在第 3 毫秒时,JavaScript 继续进行到第 5 行,将 log 函数推入堆栈,“Third”被记录到控制台中,并将该函数弹出堆栈。

大约在第 4 毫秒左右,假设文件读取任务已经完成,则相关回调函数现在会在调用栈上执行, 在回调函数内部遇到 log 函数。

log 函数推入到到调用栈,“Second”被记录到控制台并弹出 log 函数 。由于回调函数中没有更多要执行的语句,因此也被弹出 。没有更多代码可运行了 ,所以全局函数也从堆栈中删除 。

控制台输出“First”,“Third”,然后是“Second”。

Libuv 和异步操作

很明显,libuv 用于处理 Node.js 中的异步操作。对于像处理网络请求这样的异步操作,libuv 依赖于操作系统原生机制。对于没有本地 OS 支持的异步读取文件的操作,libuv 则依赖其线程池以确保主线程不被阻塞。然而,这也引发了一些问题。

  • 当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?
  • Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?
  • setTimeoutsetInterval 这类延迟执行回调函数的方法又是何时执行回调函数呢?
  • 如果 setTimeoutreadFile 这类异步任务同时完成,Node 如何决定哪个回调函数先在调用栈上运行?其中一个会有更多的优先级吗?

所有这些问题都可以通过理解 libuv 核心部分——事件循环来得到答案。

什麼是事件循環?

從技術上講,事件循環只是一個 C 語言程式。但在 Node.js 中,你可以將其視為一種設計模式,用於協調同步和非同步程式碼的執行。

視覺化事件循環

事件循環是一個循環,只要你的 Node.js 應用程式在運行,它就一直運行。每個循環中有六個不同的佇列,每個佇列都包含一個或多個需要最終在呼叫堆疊上執行的回調函數。

一起聊聊Node中的事件循環

  • 首先,有一個計時器佇列(timer queue。技術上叫最小堆(min-heap)),它保存與setTimeoutsetInterval 相關的回呼函數。
  • 其次,有一個I/O 佇列(I/O queue),其中包含與所有非同步方法相關的回呼函數,例如fshttp 模組中提供的相關方法。
  • 第三個是檢查佇列(check queue),它保存與 setImmediate 函數相關的回調函數,這是特定於Node 的功能。
  • 第四個是關閉佇列(close queue),它保存與非同步任務關閉事件相關聯的回呼函數。

最後,有兩個不同佇列組成微任務佇列(microtask queue)。

  • nextTick 佇列保存了與 process.nextTick 函數關聯的回呼函數。
  • Promise 佇列則儲存了JavaScript 中本機 Promise 相關聯的回呼函數。

要注意的是計時器、I/O、檢查和關閉佇列都屬於 libuv。然而,兩個微任務隊列並不屬於 libuv。儘管如此,它們仍然是 Node 運行時環境中扮演著重要角色,並且在執行回調順序方面發揮重要作用。說到這裡, 讓我們來理解一下事件循環是如何運作的。

事件循環是如何運作的?

圖中箭頭是一個提示,但可能還不太容易理解。讓我來解釋一下佇列的優先順序。首先要知道,所有使用者編寫的同步 JavaScript 程式碼都比非同步程式碼優先權更高。這表示只有在呼叫堆疊為空時,事件循環才會發揮作用。

在事件循環中,執行順序遵循某些規則。需要掌握的規則還是有一些的,我們逐一的了解:

  1. 執行微任務佇列(microtask queue)中的所有回呼函數。首先是 nextTick 佇列中的任務,然後是 Promise 佇列中的任務。
  2. 執行計時器佇列(timer queue)內的所有回呼函數。
  3. 如果微任務佇列中存在回呼函數,則在計時器佇列內每執行完一次回呼函數之後執行微任務佇列中的所有回呼函數。首先是 nextTick 佇列中的任務,然後是 Promise 佇列中的任務。
  4. 執行 I/O 佇列(I/O queue)內的所有回呼函數。
  5. 如果微任務佇列中存在回呼函數,請依照先 nextTick 佇列後 Promise 佇列的順序依序執行微任務佇列中的所有回呼函數。
  6. 執行檢查佇列(check queue)內的所有回呼函數。
  7. 如果微任務佇列中存在回呼函數,則在檢查佇列內每個回呼之後執行微任務佇列中的所有回呼函數 。首先是 nextTick 佇列中的任務,然後是 Promise 佇列中的任務。
  8. 執行關閉佇列(close queue)內的所有回呼函數。
  9. 在同一迴圈的最後,再執行一次微任務佇列。首先是 nextTick 佇列中的任務,然後是 Promise 佇列中的任務。

此時,如果還有更多的回呼需要處理,那麼事件循環再運行一次(譯註:事件循環在程式運行期間一直在運行,在當前沒有可供處理的任務情況下,會處於等待狀態,一旦新任務就會執行),並重複相同的步驟。另一方面,如果所有回調都已執行且沒有更多程式碼要處理(譯註:也就是程式執行結束),則事件循環退出。

這就是 libuv 事件循環在 Node.js 中執行非同步程式碼的作用。有了這些規則,我們可以重新檢視先前提出的問題。


當一個非同步任務在 libuv 中完成時,什麼時候 Node 會在呼叫堆疊上執行相關聯的回呼函數?

答案:只有當呼叫堆疊為空時才執行回呼函數。

Node 是否會等待呼叫堆疊為空後再執行回呼函數?還是打斷正常執行流來運行回呼函數?

答案:執行回呼函數時不會打斷正常執行流。

像是 setTimeoutsetInterval 這類延遲執行回呼函數的方法又是何時執行回呼函數呢?

答案:setTimeoutsetInterval 的所有回呼函數中第一優先權執行的(不考慮微任務佇列)。

如果兩個非同步任務(例如setTimeoutreadFile)同時完成,Node 如何決定那個回呼函數先在呼叫堆疊中執行?其中一個會比另一個有更高優先權嗎?

答案:在同時完成的情況下,計時器回呼會先於 I/O 回呼執行。


到此為止我們學了很多,但我希望大家可以把下面這張圖片展現的執行順序銘記於心,因為它完整的表現了Node.js 在幕後是如何執行異步程式碼的。

一起聊聊Node中的事件循環

但是,你可能會問:「驗證這個視覺化的程式碼在哪裡?」。好吧,事件循環中的每個隊列都有執行上的細微差別,因此我們最好一個個來講比較好。本文是 Node.js 事件循環系列文章中的第一篇。請務必查看文末鏈接,以便了解每個隊列中的運行細節,即便你現在在腦海中印像很深了,到具體場景的時候可能還進到一些陷阱。

結論

這個視覺指南涵蓋了 JavaScript 中非同步程式設計、Node.js 執行時期和負責處理非同步操作的 libuv 的基礎知識。有了這些知識,你可以建立一個強大的事件循環模型,在編寫利用 Node.js 非同步特性的程式碼時受益。

更多node相關知識,請造訪:nodejs 教學

以上是一起聊聊Node中的事件循環的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PHP與Vue:完美搭檔的前端開發利器 PHP與Vue:完美搭檔的前端開發利器 Mar 16, 2024 pm 12:09 PM

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

Django是前端還是後端?一探究竟! Django是前端還是後端?一探究竟! Jan 19, 2024 am 08:37 AM

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

前端面試官常問的問題 前端面試官常問的問題 Mar 19, 2024 pm 02:24 PM

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。

Go語言前端技術探秘:前端開發新視野 Go語言前端技術探秘:前端開發新視野 Mar 28, 2024 pm 01:06 PM

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

Django:前端和後端開發都能搞定的神奇框架! Django:前端和後端開發都能搞定的神奇框架! Jan 19, 2024 am 08:52 AM

Django:前端和後端開發都能搞定的神奇框架! Django是一個高效、可擴展的網路應用程式框架。它能夠支援多種Web開發模式,包括MVC和MTV,可以輕鬆地開發出高品質的Web應用程式。 Django不僅支援後端開發,還能夠快速建構出前端的介面,透過模板語言,實現靈活的視圖展示。 Django把前端開發和後端開發融合成了一種無縫的整合,讓開發人員不必專門學習

如何在JavaScript中取得HTTP狀態碼的簡單方法 如何在JavaScript中取得HTTP狀態碼的簡單方法 Jan 05, 2024 pm 01:37 PM

JavaScript中的HTTP狀態碼取得方法簡介:在進行前端開發中,我們常常需要處理與後端介面的交互,而HTTP狀態碼就是其中非常重要的一部分。了解並取得HTTP狀態碼有助於我們更好地處理介面傳回的資料。本文將介紹使用JavaScript取得HTTP狀態碼的方法,並提供具體程式碼範例。一、什麼是HTTP狀態碼HTTP狀態碼是指當瀏覽器向伺服器發起請求時,服務

Golang與前端技術結合:探討Golang如何在前端領域發揮作用 Golang與前端技術結合:探討Golang如何在前端領域發揮作用 Mar 19, 2024 pm 06:15 PM

Golang與前端技術結合:探討Golang如何在前端領域發揮作用,需要具體程式碼範例隨著互聯網和行動應用的快速發展,前端技術也愈發重要。而在這個領域中,Golang作為一門強大的後端程式語言,也可以發揮重要作用。本文將探討Golang如何與前端技術結合,以及透過具體的程式碼範例來展示其在前端領域的潛力。 Golang在前端領域的角色作為一門高效、簡潔且易於學習的

See all articles