JavaScript 事件循環

事件循環是理解 JavaScript 最重要的方面之一。本文將以簡單的方式解釋它。 介紹 阻塞事件循環 調用堆棧 簡單的事件循環解釋 排隊執行函數 消息隊列 ES6 任務隊列 介紹 了解 JavaScript 的事件循環是非常重要的。 我已經用 JavaScript 編程多年了,但我從來沒有完全理解它的運作原理。不知道這個概念的詳細細節是完全正常的,但通常還是有助於了解它的運作方式,而且你可能對此有點好奇。 本文旨在解釋 JavaScript 的內部細節,介紹單線程如何處理異步函數。 JavaScript 代碼在單線程運行,一次只能做一件事。 這是一個實際上非常有用的限制,因為它簡化了編程,不必擔心並發問題。 只需要注意如何編寫代碼,避免阻塞線程的任何操作,如同步網絡調用或無限循環。 通常情況下,大多數瀏覽器每個瀏覽器選項卡都有一個事件循環,以使每個過程都是獨立的,避免無限循環或繁重處理的網頁阻塞整個瀏覽器。 環境管理多個並發的事件循環,以處理 API 調用。Web Workers 也在自己的事件循環中運行。 你主要需要關注的是,你的代碼會在單個事件循環中運行,要以此為依據編寫代碼,避免對其進行阻塞。 阻塞事件循環 任何需要太長時間才能將控制權歸還給事件循環的 JavaScript 代碼都會阻塞頁面中的任何 JavaScript 代碼的執行,甚至會阻塞 UI 线程,用戶無法進行點擊、滾動頁面等操作。 在 JavaScript 中,幾乎所有 I/O 原語都是非阻塞的。網絡請求、Node.js 文件系統操作等都是非阻塞的。阻塞是例外,這就是為什麼 JavaScript 在回調、近來在 promises 和 async/await 上投入了大量資源的原因。 調用堆棧 調用堆棧是一個 LIFO(「後進先出」)隊列。 事件循環不斷檢查 調用堆棧 是否有需要運行的函數。 在此過程中,它將堆棧中找到的所有調用放入調用堆棧並按順序執行。 你可能熟悉調試器或瀏覽器控制台中的錯誤堆棧跟踪,瀏覽器會在堆棧中查找函數名以通知你當前調用的函數: 簡單的事件循環解釋 舉個例子: 我將使用 foo、bar 和 baz 作為隨機名稱。請輸入任意名稱以替換它們 const bar = () => console....

Node.js事件循環

事件循環是了解Node.js最重要的方面之一。 為什麼這個很重要?因為它解釋了Node.js為什麼可以是非同步的並且具有非阻塞的I/O,也解釋了Node.js的"殺手級應用",這正是使它變得如此成功的原因。 Node.js的JavaScript代碼運行在單個線程上,一次只能發生一件事。 這實際上是一種很有幫助的限制,因為它簡化了編程,不需要擔心並發問題。 您只需注意如何編寫代碼,避免阻塞線程的任何事情,例如同步網絡調用或無限循環。 總的來說,在大多數瀏覽器中,每個瀏覽器標簽都有一個事件循環,以確保每個進程都是隔離的,避免網頁中的無限循環或重型處理導致整個瀏覽器被阻塞。 環境管理多個並發的事件循環,以處理API調用等。Web Worker也運行在它們自己的事件循環中。 您只需要關心您的代碼將運行在一個單獨的事件循環上,並根據這一點來編寫代碼,以避免阻塞它。 阻塞事件循環 任何需要太長時間才能將控制權交還給事件循環的JavaScript代碼將會阻塞頁面上的任何JavaScript代碼的執行,甚至阻塞UI線程,使用戶無法點擊、滾動頁面等。 在JavaScript中,幾乎所有的I/O原語都是非阻塞的,例如網絡請求、文件系統操作等。阻塞是例外,這就是為什麼JavaScript在很大程度上基於回調,最近則是基於Promise和async/await的原因。 調用堆疊 調用堆疊是一個後進先出(LIFO)的隊列。 事件循環會不斷檢查調用堆疊,以查看是否有需要運行的函數。 在這個過程中,它將找到的每個函數調用添加到調用堆疊中,並按順序執行每個函數。 您可能熟悉調試器或瀏覽器控制台中的錯誤堆疊跟踪,瀏覽器通過查找調用堆疊中的函數名稱來告訴您哪個函數產生了當前的調用,如下圖所示: 一個簡單的事件循環解釋 讓我們舉個例子: const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') bar() baz() } foo() 這段代碼將打印出: foo bar baz 如預期一樣。 當代碼運行時,首先調用foo(),在foo()內部,我們首先調用bar(),然後調用baz()。 此時,調用堆疊如下: 事件循環在每次迭代時檢查調用堆疊,並執行其中的所有函數,直到調用堆疊為空。 排隊函數的執行 上面的示例看起來很正常,沒有什麼特別之處,JavaScript找到要執行的東西,按順序執行它們。 讓我們來看看如何延遲一個函數,直到堆疊清空。 setTimeout(() => {}, 0)的用法是調用一個函數,但是在代碼中的其他函數執行完畢之後再執行它。 請看下面的例子: const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console....

理解 process.nextTick()

Node.js的 process.nextTick 函数以特殊的方式与事件循环进行交互。 当你尝试理解 Node.js 事件循环 时,其中一个重要部分就是 process.nextTick()。 每次事件循环完成一次循环,我们称之为一次 tick。 当我们将一个函数传递给 process.nextTick() 时,我们指示引擎在当前操作结束后,在下一个事件循环 tick 开始之前调用该函数: process.nextTick(() => { // 做一些事情 }) 事件循环正在忙于处理当前函数的代码。 当此操作结束时,JS 引擎会运行在该操作期间传递给 nextTick 调用的所有函数。 这是我们告诉 JS 引擎以异步方式处理函数的一种方法(在当前函数之后),但尽快执行而不是将其排队。 调用 setTimeout(() => {}, 0) 会在下一个 tick 结束时执行函数,比使用 nextTick() 要晚得多,后者优先调用并在下一个 tick 开始之前执行。 在下一个事件循环迭代中,使用 nextTick() 可确保代码已经被执行。