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....