<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JavaScript 事件循環(huán):從起源到瀏覽器再到 Node.js

          共 8123字,需瀏覽 17分鐘

           ·

          2020-09-21 16:12

          授權(quán)轉(zhuǎn)載自:Node地下鐵

          https://mp.weixin.qq.com/s/OudqDff3QvmBqIiD74LrTw


          很多文章都在討論事件循環(huán) (Event Loop) 是什么,而幾乎沒有人討論為什么 JavaScript 中會有事件循環(huán)。博主認(rèn)為這是為什么很多人都不能很好理解事件循環(huán)的一個(gè)重要原因 —— 知其然不知其所以然。所以本文試圖拋磚引玉,從一些更溯源的方式來與大家探討 event loop,希望大家能從中有些收獲。

          本文從三個(gè)角度來研究 JavaScript 的事件循環(huán):

          1. 為什么是事件循環(huán)

          2. 事件循環(huán)是什么

          3. 瀏覽器與 Node.js 的事件循環(huán)差異

          為什么是事件循環(huán)

          JavaScript 是網(wǎng)景 (Netscape) 公司為其旗下的網(wǎng)景瀏覽器提供更復(fù)雜網(wǎng)頁交互時(shí)所推出的一個(gè)動態(tài)腳本語言。其創(chuàng)作者 Eich 在 10 天內(nèi)寫出了 JavaScript 的第一個(gè)版本,通過 Eich 在 JavaScript 20 周年的演講 [1] 回顧中,我們可以發(fā)現(xiàn) JavaScript 在最初設(shè)計(jì)的時(shí)候沒有考慮所謂的事件循環(huán)。那么事件循環(huán)到底是怎么出現(xiàn)的?

          首先讓我們來看看引入 JavaScript 到網(wǎng)頁端的經(jīng)典用例:一個(gè)用戶打開一個(gè)網(wǎng)頁,填寫完表單提交之后,等待 30s 的白屏之后發(fā)現(xiàn)表單中的某個(gè)地方填寫錯誤了需要重新填寫。在這個(gè)場景中,如果我們有 JavaScript 就可以在用戶提交表單之前先在用戶本地的瀏覽器端做一次校驗(yàn),避免用戶每次都通過網(wǎng)絡(luò)找服務(wù)端來校驗(yàn)所浪費(fèi)的時(shí)間。

          分析一下這個(gè)場景,我們就可以發(fā)現(xiàn),最早的 JavaScript 的執(zhí)行就是用戶通過瀏覽器的事件來觸發(fā)的,例如用戶填寫完表單之后點(diǎn)擊提交的時(shí)候,瀏覽器觸發(fā)一個(gè) DOM 的點(diǎn)擊事件,而點(diǎn)擊事件綁定了對應(yīng)的 JavaScript 代碼來執(zhí)行校驗(yàn)的過程。在這個(gè)過程中,JavaScript 的代碼都是被動被調(diào)用的。

          仔細(xì)思考一下就會發(fā)現(xiàn),JavaScript 所謂的事件和觸發(fā)本質(zhì)上都通過瀏覽器中轉(zhuǎn),更像是瀏覽器行為而不僅僅是 JavaScript 語言內(nèi)的一個(gè)隊(duì)列。順著這個(gè)思路我們順藤摸瓜,就會發(fā)現(xiàn) EcmaScript 的標(biāo)準(zhǔn)定義中壓根 就沒有事件循環(huán),反倒是 HTML 的標(biāo)準(zhǔn) [2]?中定義了事件循環(huán)(目前 HTML 有 whatwg 和 w3c 標(biāo)準(zhǔn),這里討論的是 wahtwg 的標(biāo)準(zhǔn)):

          To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Each agent has an associated event loop, which is unique to that agent.

          根據(jù)標(biāo)準(zhǔn)中對事件循環(huán)的定義描述,我們可以發(fā)現(xiàn)事件循環(huán)本質(zhì)上是 user agent (如瀏覽器端) 用于協(xié)調(diào)用戶交互(鼠標(biāo)、鍵盤)、腳本(如 JavaScript)、渲染(如 HTML DOM、CSS 樣式)、網(wǎng)絡(luò)等行為的一個(gè)機(jī)制。

          了解到這個(gè)定義之后,我們就能夠清楚的明白,與其說是 JavaScript 提供了事件循環(huán),不如說是嵌入 JavaScript 的 user agent 需要通過事件循環(huán)來與多種事件源交互。

          事件循環(huán)是什么

          我們知道了事件循環(huán)的本質(zhì)是一個(gè) user agent 上協(xié)調(diào)各類事件的機(jī)制,這一節(jié)我們主要討論一下瀏覽器中的這個(gè)機(jī)制與 JavaScript 的交互部分。

          各種瀏覽器事件同時(shí)觸發(fā)時(shí),肯定有一個(gè)先來后到的排隊(duì)問題。決定這些事件如何排隊(duì)觸發(fā)的機(jī)制,就是事件循環(huán)。這個(gè)排隊(duì)行為以 JavaScript 開發(fā)者的角度來看,主要是分成兩個(gè)隊(duì)列:

          • 一個(gè)是 JavaScript 外部的隊(duì)列。外部的隊(duì)列主要是瀏覽器協(xié)調(diào)的各類事件的隊(duì)列,標(biāo)準(zhǔn)文件中稱之為?Task Queue。下文中為了方便理解統(tǒng)一稱為外部隊(duì)列

          • 另一個(gè)是 JavaScript 內(nèi)部的隊(duì)列。這部分主要是 JavaScript 內(nèi)部執(zhí)行的任務(wù)隊(duì)列,標(biāo)準(zhǔn)中稱之為?Microtask Queue。下文中為了方便理解統(tǒng)一稱為內(nèi)部隊(duì)列。

          值得注意的是,雖然為了好理解我們管這個(gè)叫隊(duì)列 (Queue),但是本質(zhì)上是有序集合 (Set),因?yàn)閭鹘y(tǒng)的隊(duì)列都是先進(jìn)先出(FIFO)的,而這里的隊(duì)列則不然,排到最前面但是沒有滿足條件也是不會執(zhí)行的(比如外部隊(duì)列里只有一個(gè) setTimeout 的定時(shí)任務(wù),但是時(shí)間還沒有到,沒有滿足條件也不會把他出列來執(zhí)行)。

          外部隊(duì)列

          外部隊(duì)列(Task Queue [3]),顧名思義就是 JavaScript 外部的事件的隊(duì)列,這里我們可以先列舉一下瀏覽器中這些外部事件源(Task Source),他們主要有:

          • DOM 操作 (頁面渲染)

          • 用戶交互 (鼠標(biāo)、鍵盤)

          • 網(wǎng)絡(luò)請求 (Ajax 等)

          • History API 操作

          • 定時(shí)器 (setTimeout 等) [4]

          可以觀察到,這些外部的事件源可能很多,為了方便瀏覽器廠商優(yōu)化,HTML 標(biāo)準(zhǔn)中明確指出一個(gè)事件循環(huán)由一個(gè)或多個(gè)外部隊(duì)列,而每一個(gè)外部事件源都有一個(gè)對應(yīng)的外部隊(duì)列。不同事件源的隊(duì)列可以有不同的優(yōu)先級(例如在網(wǎng)絡(luò)事件和用戶交互之間,瀏覽器可以優(yōu)先處理鼠標(biāo)行為,從而讓用戶感覺更加流程)。

          內(nèi)部隊(duì)列

          內(nèi)部隊(duì)列(Microtask Queue),即 JavaScript 語言內(nèi)部的事件隊(duì)列,在 HTML 標(biāo)準(zhǔn)中,并沒有明確規(guī)定這個(gè)隊(duì)列的事件源,通常認(rèn)為有以下幾種:

          • Promise [5] 的成功 (.then) 與失敗 (.catch)

          • MutationObserver [6]

          • Object.observe?(已廢棄) [7]

          處理模型

          在標(biāo)準(zhǔn)定義中事件循環(huán)的步驟比較復(fù)雜,這里我們簡單描述一下這個(gè)處理過程:

          1. 從外部隊(duì)列中取出一個(gè)可執(zhí)行任務(wù),如果有則執(zhí)行,沒有下一步。

          2. 挨個(gè)取出內(nèi)部隊(duì)列中的所有任務(wù)執(zhí)行,執(zhí)行完畢或沒有則下一步。

          3. 瀏覽器渲染。

          案例分析

          根據(jù)上述的處理模型,我們可以來看以下例子:

          console.log('script start');
          setTimeout(function() { console.log('setTimeout');}, 0);
          Promise.resolve().then(function() { console.log('promise1');}).then(function() { console.log('promise2');});
          console.log('script end');

          輸出結(jié)果:

          script start
          script end
          promise1
          promise2
          setTimeout


          對應(yīng)的處理過程則是:

          1. 執(zhí)行 console.log (輸出 script start)

          2. 遇到 setTimeout 加入外部隊(duì)列

          3. 遇到兩個(gè) Promise 的 then 加入內(nèi)部隊(duì)列

          4. 遇到 console.log 直接執(zhí)行(輸出 script end)

          5. 內(nèi)部隊(duì)列中的任務(wù)挨個(gè)執(zhí)行完 (輸出 promise1 和 promise2)

          6. 外部隊(duì)列中的任務(wù)執(zhí)行 (輸出 setTimeout)

          只要理解了外部隊(duì)列與內(nèi)部隊(duì)列的概念,再看這類問題就會變得很簡單,我們再簡單擴(kuò)展看看:

          setTimeout(() => {  console.log('setTimeout1')})
          Promise.resolve().then(() => { console.log('promise1')})
          setTimeout(() => { console.log('setTimeout2')})
          Promise.resolve().then(() => { console.log('promise2')})
          Promise.resolve().then(() => { console.log('promise3')})
          console.log('script end');

          結(jié)果輸出

          script end
          promise1
          promise2
          promise3
          setTimeout1
          setTimeout2


          可以發(fā)現(xiàn)加入內(nèi)部隊(duì)列的順序和時(shí)間雖然前后差異,但是輪到內(nèi)部隊(duì)列執(zhí)行的時(shí)候,一定會先全部執(zhí)行完內(nèi)部隊(duì)列才會繼續(xù)往下走去執(zhí)行外部隊(duì)列的任務(wù)。

          最后我們再看一個(gè)引入了 HTML 渲染的例子:

                      

          通過這個(gè)例子,我們就可以發(fā)現(xiàn),渲染過程很明顯分成三個(gè)階段:

          1. JavaScript 執(zhí)行完畢 innerText 首先加上 [end 10001]

          2. 內(nèi)部隊(duì)列:Promise 的 then 全部任務(wù)執(zhí)行完畢,往 innerText 上追加了很長一段字符串

          3. HTML 渲染:1 和 2 追加到 innerText 上的內(nèi)容同時(shí)渲染

          4. 外部隊(duì)列:挨個(gè)執(zhí)行 setTimeout 中追加到 innerText 的內(nèi)容

          5. HTML 渲染:將 4 中的內(nèi)容渲染。

          6. 回到第 4 步走外部隊(duì)列的流程(內(nèi)部隊(duì)列已清空)

          script 事件是外部隊(duì)列

          有的同學(xué)看完上面的幾個(gè)例子之后可能有個(gè)問題,為什么 JavaScript 代碼執(zhí)行到 script end 之后,是先執(zhí)行內(nèi)部隊(duì)列然后再執(zhí)行外部隊(duì)列的任務(wù)?

          這里不得不把上文中出現(xiàn)過的 HTML 事件循環(huán)標(biāo)準(zhǔn) [2] 再拉出來一遍:

          To coordinate events, user interaction,?scripts, rendering, networking, and so forth, user agents must use event loops as described in this section...

          看到這里,大家可能就反應(yīng)過來了,scripts 執(zhí)行也是一個(gè)事件,我們只要?dú)w類一下就會發(fā)現(xiàn) JavaScript 的執(zhí)行也是一個(gè)瀏覽器發(fā)起的外部事件。所以本質(zhì)的執(zhí)行順序還是:

          1. 一次外部事件

          2. 所有內(nèi)部事件

          3. HTML 渲染

          4. 回到到 1

          瀏覽器與 Node.js 的事件循環(huán)差異

          根據(jù)本文開頭我們討論的事件循環(huán)起源,很容易理解為什么瀏覽器與 Node.js 的事件循環(huán)會存在差異。如果說瀏覽端是將 JavaScript 集成到 HTML 的事件循環(huán) [2] 之中,那么 Node.js 則是將 JavaScript 集成到 libuv 的 I/O 循環(huán) [8] 之中。

          簡而言之,二者都是把 JavaScript 集成到他們各自的環(huán)境中,但是 HTML (瀏覽器端) 與 libuv (服務(wù)端) 面對的場景有很大的差異。首先能直觀感受到的區(qū)別是:

          1. 事件循環(huán)的過程沒有 HTML 渲染。只剩下了外部隊(duì)列和內(nèi)部隊(duì)列這兩個(gè)部分。

          2. 外部隊(duì)列的事件源不同。Node.js 端沒有了鼠標(biāo)等外設(shè)但是新增了文件等 IO。

          3. 內(nèi)部隊(duì)列的事件僅剩下 Promise 的 then 和 catch。

          至于內(nèi)在的差異,有一個(gè)很重要的地方是 Node.js (libuv)在最初設(shè)計(jì)的時(shí)候是允許執(zhí)行多次外部的事件再切換到內(nèi)部隊(duì)列的,而瀏覽器端一次事件循環(huán)只允許執(zhí)行一次外部事件。這個(gè)經(jīng)典的內(nèi)在差異,可以通過以下例子來觀察:

          setTimeout(()=>{  console.log('timer1');  Promise.resolve().then(function() {      console.log('promise1');  });});
          setTimeout(()=>{ console.log('timer2'); Promise.resolve().then(function() { console.log('promise2'); });});

          這個(gè)例子在瀏覽器端執(zhí)行的結(jié)果是?timer1?->?promise1?->?timer2?->?promise2,而在 Node.js 早期版本(11 之前)執(zhí)行的結(jié)果卻是?timer1?->?timer2?->?promise1?->?promise2。

          究其原因,主要是因?yàn)闉g覽器端有外部隊(duì)列一次事件循環(huán)只能執(zhí)行一個(gè)的限制,而在 Node.js 中則放開了這個(gè)限制,允許外部隊(duì)列中所有任務(wù)都執(zhí)行完再切換到內(nèi)部隊(duì)列。所以他們的情況對應(yīng)為:

          • 瀏覽器端

            1. 外部隊(duì)列:代碼執(zhí)行,兩個(gè) timeout 加入外部隊(duì)列

            2. 內(nèi)部隊(duì)列:空

            3. 外部隊(duì)列:第一個(gè) timeout 執(zhí)行,promise 加入內(nèi)部隊(duì)列

            4. 內(nèi)部隊(duì)列:執(zhí)行第一個(gè) promise

            5. 外部隊(duì)列:第二個(gè) timeout 執(zhí)行,promise 加入內(nèi)部隊(duì)列

            6. 內(nèi)部隊(duì)列:執(zhí)行第二個(gè) promise

          • Node.js 服務(wù)端

            1. 外部隊(duì)列:代碼執(zhí)行,兩個(gè) timeout 加入外部隊(duì)列

            2. 內(nèi)部隊(duì)列:空

            3. 外部隊(duì)列:兩個(gè) timeout 都執(zhí)行完

            4. 內(nèi)部隊(duì)列:兩個(gè) promise 都執(zhí)行完

          雖然 Node.js 的這個(gè)問題在 11 之后的版本里修復(fù)了,但是為了繼續(xù)探究這個(gè)影響,我們引入一個(gè)新的外部事件 setImmediate。這個(gè)方法目前是 Node.js 獨(dú)有的,瀏覽器端沒有。

          setImmediate 的引入是為了解決 setTimeout 的精度問題,由于 setTimeout 指定的延遲時(shí)間是毫秒(ms)但實(shí)際一次時(shí)間循環(huán)的時(shí)間可能是納秒級的,所以在一次事件循環(huán)的多個(gè)外部隊(duì)列中,找到某一個(gè)隊(duì)列直接執(zhí)行其中的 callback 可以得到比 setTimeout 更早執(zhí)行的效果。我們繼續(xù)以開始的場景構(gòu)造一個(gè)例子,并在 Node.js 10.x 的版本上執(zhí)行(存在一次事件循環(huán)執(zhí)行多次外部事件):

          setTimeout(()=>{  console.log('setTimeout1');  Promise.resolve().then(() => console.log('promise1'));});
          setTimeout(()=>{ console.log('setTimeout2'); Promise.resolve().then(() => console.log('promise2'));});
          setImmediate(() => { console.log('setImmediate1'); Promise.resolve().then(() => console.log('promise3'));});
          setImmediate(() => { console.log('setImmediate2'); Promise.resolve().then(() => console.log('promise4'));});

          輸出結(jié)果:

          setImmediate1
          setImmediate2
          promise3
          promise4
          setTimeout1
          setTimeout2
          promise1
          promise2


          根據(jù)這個(gè)執(zhí)行結(jié)果 [12],我們可以推測出 Node.js 中的事件循環(huán)與瀏覽器類似,也是外部隊(duì)列與內(nèi)部隊(duì)列的循環(huán),而 setImmediate 在另外一個(gè)外部隊(duì)列中。

          接下來,我們再來看一下當(dāng) Node.js 在與瀏覽器端對齊了事件循環(huán)的事件之后,這個(gè)例子的執(zhí)行結(jié)果為:

          setImmediate1
          promise3
          setImmediate2
          promise4
          setTimeout1
          promise1
          setTimeout2
          promise2


          其中主要有兩點(diǎn)需要關(guān)注,一是外部列隊(duì)在每次事件循環(huán)只執(zhí)行了一個(gè),另一個(gè)是 Node.js 的固定了多個(gè)外部隊(duì)列的優(yōu)先級。setImmediate 的外部隊(duì)列沒有執(zhí)行完的時(shí)候,是不會執(zhí)行 timeout 的外部隊(duì)列的。了解了這個(gè)點(diǎn)之后,Node.js 的事件循環(huán)就變得很簡單了,我們可以看下 Node.js 官方文檔 [9] 中對于事件循環(huán)順序的展示:

          其中 check 階段是用于執(zhí)行 setImmediate 事件的。結(jié)合本文上面的推論我們可以知道,Node.js 官方這個(gè)所謂事件循環(huán)過程,其實(shí)只是完整的事件循環(huán)中 Node.js 的多個(gè)外部隊(duì)列相互之間的優(yōu)先級順序。

          我們可以在加入一個(gè) poll 階段的例子來看這個(gè)循環(huán):

          const fs = require('fs');
          setImmediate(() => { console.log('setImmediate');});
          fs.readdir(__dirname, () => { console.log('fs.readdir');});
          setTimeout(()=>{ console.log('setTimeout');});
          Promise.resolve().then(() => { console.log('promise');});


          輸出結(jié)果(v12.x):

          promise
          setTimeout
          fs.readdir
          setImmediate


          根據(jù)輸出結(jié)果,我們可以知道梳理出來:

          1. 外部隊(duì)列:執(zhí)行當(dāng)前 script

          2. 內(nèi)部隊(duì)列:執(zhí)行 promise

          3. 外部隊(duì)列:執(zhí)行 setTimeout

          4. 內(nèi)部隊(duì)列:空

          5. 外部隊(duì)列:執(zhí)行 fs.readdir

          6. 內(nèi)部隊(duì)列:空

          7. 外部隊(duì)列:執(zhí)行 check (setImmediate)

          這個(gè)順序符合 Node.js 對其外部隊(duì)列的優(yōu)先級定義:

          timer(setTimeout)是第一階段的原因在 libuv 的文檔 [8] 中有描述 —— 為了減少時(shí)間相關(guān)的系統(tǒng)調(diào)用(System Call)。setImmediate 出現(xiàn)在 check 階段是蹭了 libuv 中 poll 階段之后的檢查過程(這個(gè)過程放在 poll 中也很奇怪,放在 poll 之后感覺比較合適)。

          idle, prepare?對應(yīng)的是 libuv 中的兩個(gè)叫做 idle [10] 和 prepare [11] 的句柄。由于 I/O 的 poll 過程可能阻塞住事件循環(huán),所以這兩個(gè)句柄主要是用來觸發(fā) poll (阻塞)之前需要觸發(fā)的回調(diào):

          由于 poll 可能 block 住事件循環(huán),所以應(yīng)當(dāng)有一個(gè)外部隊(duì)列專門用于執(zhí)行 I/O 的 callback ,并且優(yōu)先級在 poll 以及 prepare to poll 之前。

          另外我們知道網(wǎng)絡(luò) IO 可能有非常多的請求同時(shí)進(jìn)來,如果該階段如果無限制的執(zhí)行這些 callback,可能導(dǎo)致 Node.js 的進(jìn)程卡死該階段,其他外部隊(duì)列的代碼都沒執(zhí)行了。所以當(dāng)前外部隊(duì)列在執(zhí)行一定數(shù)量的 callback 之后會截?cái)唷S捎?/span>截?cái)?span style="background-color: rgb(255, 255, 255);">的這個(gè)特性,這個(gè)專門執(zhí)行 I/O callbacks 的外部隊(duì)列也叫?pengding callbacks


             ┌───────────────────────────┐
          ┌─>│ timers │
          │ └─────────────┬─────────────┘
          │ ┌─────────────┴─────────────┐
          │ │ pending callbacks │
          │ └─────────────┬─────────────┘
          │ ┌─────────────┴─────────────┐
          │ │ idle, prepare │
          │ └─────────────┬─────────────┘ ┌───────────────┐
          │ ┌─────────────┴─────────────┐ │ incoming: │
          │ │ poll │<─────┤ connections, │
          │ └─────────────┬─────────────┘ │ data, etc. │
          │ ┌─────────────┴─────────────┐ └───────────────┘
          └──│ check │
          └───────────────────────────┘


          至此 Node.js 多個(gè)外部隊(duì)列的優(yōu)先級已經(jīng)演化到類似原版的程度。最后剩下的 socket close 為什么是在 check 和 timers 之間,這個(gè)具體的權(quán)衡留待大家一起探討,博主這里已經(jīng)肝不動了。

          關(guān)于瀏覽器與 Node.js 的事件循環(huán),如果你要問我那邊更加簡單,那么我肯定會說是 Node.js 的事件循環(huán)更加簡單,因?yàn)樗亩鄠€(gè)外部隊(duì)列是可枚舉的并且優(yōu)先級是固定的。但是瀏覽器端在對它的多個(gè)外部隊(duì)列做優(yōu)先級排列的時(shí)候,我們一沒法枚舉,二不清楚其優(yōu)先級策略,甚至瀏覽器端的事件循環(huán)可能是基于多線程或者多進(jìn)程的(HTML 的標(biāo)準(zhǔn)中并沒有規(guī)定一定要使用單線程來實(shí)現(xiàn)事件循環(huán))。


          小結(jié)

          我們都知道瀏覽器端是直面用戶的,這也意味著瀏覽器端會更加注重用戶的體驗(yàn)(如可見性、可交互性),如果有一個(gè)優(yōu)化效果是能夠極大的減少 JavaScript 的執(zhí)行時(shí)間,但要消耗更多 HTML 渲染的時(shí)間的話,通常來說我們都不會做這個(gè)優(yōu)化。通過這個(gè)例子來觀察,可以發(fā)現(xiàn)我們在瀏覽器并不是主要關(guān)注某件事整體所消耗的時(shí)間是否更少,而是用戶是否能快的體驗(yàn)到交互(感受到 HTML 渲染)。而到了 Node.js 這個(gè)服務(wù)端 JavaScript 的場景下,這一點(diǎn)是明確不一樣的。在服務(wù)端為了保持應(yīng)用的流暢,早期甚至出現(xiàn)了一次事件循環(huán)執(zhí)行多個(gè)外部事件的優(yōu)化方式。

          很多同學(xué)在理解事件循環(huán)時(shí)感到隔靴搔癢的一個(gè)重要原因,便是把事件循環(huán)與 JavaScript 的關(guān)系弄錯了。JavaScript 的事件循環(huán)與其說是 JavaScript 的語言特性,更準(zhǔn)確的理解應(yīng)該是某個(gè)設(shè)備/端(如瀏覽器)的事件循環(huán)中與 JavaScript 交互的部分。

          造成瀏覽器端與 Node.js 端事件循環(huán)的差異的一個(gè)很大的原因在于 。事件循環(huán)的設(shè)計(jì)初衷更多的是方便 JavaScript 與其嵌入環(huán)境的交互,所以事件循環(huán)如何運(yùn)作,也更多的會受到 JavaScript 嵌入環(huán)境的影響,不同的設(shè)備、嵌入式環(huán)境甚至是不同的瀏覽器都會有各自的想法。


          注 [1]:??Eich?在 JavaScript 20 周年的演講 youtube 需翻墻,地址?https://www.youtube.com/watch?v=83_rC1FesOI

          注 [2]:?whatwg?HTML 標(biāo)準(zhǔn)?https://html.spec.whatwg.org/multipage/webappapis.html#event-loops

          注 [3]: 關(guān)于 Task,常有人稱它為 Marcotask,但 HTML 標(biāo)準(zhǔn)中沒有這種說法。

          注 [4]: 定時(shí)器操作主要依賴 JavaScript 外部的 agent 實(shí)現(xiàn)。所以歸類為外部事件。

          注 [5]: MDN Promise 文檔?https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

          注 [6]:?MDN?MutationObserver 文檔?https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver

          注 [7]: MDN?Object.observe 文檔?https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/observe

          注 [8]:?libuv 的 I/O 循環(huán)?http://docs.libuv.org/en/v1.x/design.html#the-i-o-loop

          注 [9]:?Node.js 事件循環(huán),定時(shí)器和 process.nextTick()?https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/#what-is-the-event-loop

          注 [10]:?libuv?idle 句柄?http://docs.libuv.org/en/v1.x/idle.html

          注 [11]: libuv?prepare 句柄?http://docs.libuv.org/en/v1.x/prepare.html

          注 [12]: 這里 setTimeout 在 setImmediate 后面執(zhí)行的原因是因?yàn)?ms 精度的問題,想要手動 fix 這個(gè)精度可以插入一段??const now = Date.now(); wihle (Date.now() < now + 1) {}? 即可看到 setTimeout 在 setImmediate 之前執(zhí)行了。

          其他參考文獻(xiàn):
          https://zhuanlan.zhihu.com/p/34229323
          https://juejin.im/post/5c337ae06fb9a049bc4cd218


          》》面試官都在用的題庫,快來看看《

          瀏覽 50
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一道本无码免费视频 | 黄色a电影 | 午夜性爱免费视频 | 日韩成人无码一区二区视频 | 欧美人与动Zozo禽交大全 |