javascript基礎修煉(5)—Event Loop
開發者的javascript造詣取決於對【動態】和【非同步】這兩個詞的理解水平。
一. 一道考察非同步知識的面試題
題目是這樣的,要求寫出下面程式碼的輸出:
setTimeout(() => { console.log(1) }, 0) new Promise((resolve, reject) => { console.log(2) for (let i = 0; i < 10000; i++) { i === 9999 && resolve() } console.log(3) }).then(() => { console.log(4) }) console.log(5)
如果沒有詳細鑽研過非同步佇列,答對的可能性很低。題目的考察點很明確,就是 javascript
中最核心的特點之一的【非同步】,瞭解了原理以後,你就會明白 javascript
中聲稱的“無阻塞”並不是完全成立的,通過一些小辦法就可以讓 setTimeout( )
的回撥永遠都無法被執行,儘管這看起來除了滿足整蠱需求以外並沒有什麼明顯的實用價值。
對Event Loop的理解,帶給開發者的是 對程式碼整個生命週期更精細的控制能力 ,儘管在依賴於SPA框架的開發中你幾乎不會用到它們。
二. Event Loop的原理
(上圖來自下面推薦的這篇博文)
【極力推薦文章】:
ofollow,noindex" target="_blank">https://github.com/nswbmw/node-in-debugging/blob/master/3.6%20Event%20Loop.md
並不是筆者偷懶不想寫這一節,而是在讀過了這篇教程以後,自認為除非是剖析更底層的 libuv
的原理,否則僅就理解Event Loop而言,筆者自己認為不會比這篇寫的清晰。
三. 解析最後一題
上文中給出了從簡單到複雜共6道題來供讀者自檢,算是非常貼心了,本文中針對最後一題進行一些講解。 你會發現只要理解了Event Loop 的基本原理後,分析這類程式碼基本就是一個【完形填空】的過程 。
題目如下:
setImmediate(() => { console.log(1) setTimeout(() => { console.log(2) }, 100) setImmediate(() => { console.log(3) }) process.nextTick(() => { console.log(4) }) }) process.nextTick(() => { console.log(5) setTimeout(() => { console.log(6) }, 100) setImmediate(() => { console.log(7) }) process.nextTick(() => { console.log(8) }) }) console.log(9)
題目分析:
為了方便分析,先做程式碼分塊:
將程式碼塊放入事件迴圈:
分析:
這裡有必要說明一下 Fn2
的位置,文中並沒有明確提及同步程式碼執行完畢後進入非同步佇列時會先經歷 Tick
階段,就圖示而言,每一個巨集觀任務階段之間都會檢查 Tick
佇列(你也可以理解為每次函式的呼叫棧被清空的時候會檢查一次 Tick
佇列),那麼 Fn2
的待執行時序也就很好理解了。為了方便分析,將 console.log(n)
相關的方法稱為 cln
。
接下來看一下當執行至 Fn2
時發生的事情:

分析:
Tick
佇列中的 process.nextTick( )
回撥會直接加入 Tick
佇列(此處就可以實現篇頭講到的阻塞事件迴圈)。另外講一下 CL6
這個回撥,它上面綁定了一個100ms的定時器,在後續的 Timers
和 IO Polling
中都會檢查倒計時是否到期,到了就執行,沒到就等下一次 Timers
或 IO Polling
階段再檢查。從上例來看,推遲100ms的 CL6
在沒有其他干擾的情況下幾乎一定會在N個event loop以後才被執行。
同樣的道理來拆分一下 Fn1
:
分析:
CL6
比 CL2
先開始計時,所以倒計時100ms先到,當然這是N個事件迴圈以後的事情了。
所以從上面的時序就可以看到輸出的結果: 9 5 8 1 7 4 3 6 2
【思考題】:
外加一個思考題,如果上例中 CL6
和 CL2
的延遲都是0,結果是怎樣的呢?
四. requestAnimationFrame
requestAnimationFrame()
很多時候會被拿來和 setTimeout()
作對比,這個API是瀏覽器環境下為了實現高效能幀動畫而設計的,它的主要目的是為了讓瀏覽器的重繪能夠配合顯示裝置的重新整理率而去掉不必要的效能開銷,常見的顯示裝置重新整理率為 60Hz
,相當於你每1000/60≈16.7ms只能看螢幕一眼,得到的資訊都是依靠這些離散畫面的視覺暫留拼湊起來的,那如果動畫元素在你看螢幕的時間間隔中畫素位移過大的話,看起來就會是一卡一卡的,也就是平時常說的“丟幀”,從Event Loop的角度來講的話,將其近似理解為 setTimeout(fn, 1000/重新整理率)
就可以了。
編輯/尋水的魚
本文首發於華為雲社群: 原文連結