成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

一篇文章教會你Event loop——瀏覽器和Node

Leck1e / 2012人閱讀

摘要:如果沒到毫秒,那么階段就會跳過,進(jìn)入階段,先執(zhí)行的回調(diào)函數(shù)。參考文檔什么是瀏覽器的事件循環(huán)不要混淆和瀏覽器中的定時(shí)器詳解瀏覽器和不同的事件循環(huán)深入理解事件循環(huán)機(jī)制篇中的執(zhí)行機(jī)制

最近對Event loop比較感興趣,所以了解了一下。但是發(fā)現(xiàn)整個(gè)Event loop盡管有很多篇文章,但是沒有一篇可以看完就對它所有內(nèi)容都了解的文章。大部分的文章都只闡述了瀏覽器或者Node二者之一,沒有對比的去看的話,認(rèn)識總是淺一點(diǎn)。所以才有了這篇整理了百家之長的文章。
1. 定義
Event loop:為了協(xié)調(diào)事件(event),用戶交互(user interaction),腳本(script),渲染(rendering),網(wǎng)絡(luò)(networking)等,用戶代理(user agent)必須使用事件循環(huán)(event loops)。(3月29修訂)

那什么是事件?

事件:事件就是由于某種外在或內(nèi)在的信息狀態(tài)發(fā)生的變化,從而導(dǎo)致出現(xiàn)了對應(yīng)的反應(yīng)。比如說用戶點(diǎn)擊了一個(gè)按鈕,就是一個(gè)事件;HTML頁面完成加載,也是一個(gè)事件。一個(gè)事件中會包含多個(gè)任務(wù)。

我們在之前的文章中提到過,JavaScript引擎又稱為JavaScript解釋器,是JavaScript解釋為機(jī)器碼的工具,分別運(yùn)行在瀏覽器和Node中。而根據(jù)上下文的不同,Event loop也有不同的實(shí)現(xiàn):其中Node使用了libuv庫來實(shí)現(xiàn)Event loop; 而在瀏覽器中,html規(guī)范定義了Event loop,具體的實(shí)現(xiàn)則交給不同的廠商去完成。

所以,瀏覽器的Event loop和Node的Event loop是兩個(gè)概念,下面分別來看一下。

2. 意義

在實(shí)際工作中,了解Event loop的意義能幫助你分析一些異步次序的問題(當(dāng)然,隨著es7 async和await的流行,這樣的機(jī)會越來越少了)。除此以外,它還對你了解瀏覽器和Node的內(nèi)部機(jī)制有積極的作用;對于參加面試,被問到一堆異步操作的執(zhí)行順序時(shí),也不至于兩眼抓瞎。

3. 瀏覽器上的實(shí)現(xiàn)

在JavaScript中,任務(wù)被分為Task(又稱為MacroTask,宏任務(wù))和MicroTask(微任務(wù))兩種。它們分別包含以下內(nèi)容:

MacroTask: script(整體代碼), setTimeout, setInterval, setImmediate(node獨(dú)有), I/O, UI rendering
MicroTask: process.nextTick(node獨(dú)有), Promises, Object.observe(廢棄), MutationObserver

需要注意的一點(diǎn)是:在同一個(gè)上下文中,總的執(zhí)行順序?yàn)橥酱a—>microTask—>macroTask[6]。這一塊我們在下文中會講。

瀏覽器中,一個(gè)事件循環(huán)里有很多個(gè)來自不同任務(wù)源的任務(wù)隊(duì)列(task queues),每一個(gè)任務(wù)隊(duì)列里的任務(wù)是嚴(yán)格按照先進(jìn)先出的順序執(zhí)行的。但是,因?yàn)?strong>瀏覽器自己調(diào)度的關(guān)系,不同任務(wù)隊(duì)列的任務(wù)的執(zhí)行順序是不確定的。

具體來說,瀏覽器會不斷從task隊(duì)列中按順序取task執(zhí)行,每執(zhí)行完一個(gè)task都會檢查microtask隊(duì)列是否為空(執(zhí)行完一個(gè)task的具體標(biāo)志是函數(shù)執(zhí)行棧為空),如果不為空則會一次性執(zhí)行完所有microtask。然后再進(jìn)入下一個(gè)循環(huán)去task隊(duì)列中取下一個(gè)task執(zhí)行,以此類推。

注意:圖中橙色的MacroTask任務(wù)隊(duì)列也應(yīng)該是在不斷被切換著的。

本段大批量引用了《什么是瀏覽器的事件循環(huán)(Event Loop)》的相關(guān)內(nèi)容,想看更加詳細(xì)的描述可以自行取用。

4. Node上的實(shí)現(xiàn)

nodejs的event loop分為6個(gè)階段,它們會按照順序反復(fù)運(yùn)行,分別如下:

timers:執(zhí)行setTimeout() 和 setInterval()中到期的callback。

I/O callbacks:上一輪循環(huán)中有少數(shù)的I/Ocallback會被延遲到這一輪的這一階段執(zhí)行

idle, prepare:隊(duì)列的移動,僅內(nèi)部使用

poll:最為重要的階段,執(zhí)行I/O callback,在適當(dāng)?shù)臈l件下會阻塞在這個(gè)階段

check:執(zhí)行setImmediate的callback

close callbacks:執(zhí)行close事件的callback,例如socket.on("close",func)

不同于瀏覽器的是,在每個(gè)階段完成后,而不是MacroTask任務(wù)完成后,microTask隊(duì)列就會被執(zhí)行。這就導(dǎo)致了同樣的代碼在不同的上下文環(huán)境下會出現(xiàn)不同的結(jié)果。我們在下文中會探討。

另外需要注意的是,如果在timers階段執(zhí)行時(shí)創(chuàng)建了setImmediate則會在此輪循環(huán)的check階段執(zhí)行,如果在timers階段創(chuàng)建了setTimeout,由于timers已取出完畢,則會進(jìn)入下輪循環(huán),check階段創(chuàng)建timers任務(wù)同理。

5. 示例 5.1 瀏覽器與Node執(zhí)行順序的區(qū)別
setTimeout(()=>{
    console.log("timer1")

    Promise.resolve().then(function() {
        console.log("promise1")
    })
}, 0)

setTimeout(()=>{
    console.log("timer2")

    Promise.resolve().then(function() {
        console.log("promise2")
    })
}, 0)



瀏覽器輸出:
time1
promise1
time2
promise2

Node輸出:
time1
time2
promise1
promise2

在這個(gè)例子中,Node的邏輯如下:

最初timer1和timer2就在timers階段中。開始時(shí)首先進(jìn)入timers階段,執(zhí)行timer1的回調(diào)函數(shù),打印timer1,并將promise1.then回調(diào)放入microtask隊(duì)列,同樣的步驟執(zhí)行timer2,打印timer2;
至此,timer階段執(zhí)行結(jié)束,event loop進(jìn)入下一個(gè)階段之前,執(zhí)行microtask隊(duì)列的所有任務(wù),依次打印promise1、promise2。

而瀏覽器則因?yàn)閮蓚€(gè)setTimeout作為兩個(gè)MacroTask, 所以先輸出timer1, promise1,再輸出timer2,promise2。

更加詳細(xì)的信息可以查閱《深入理解js事件循環(huán)機(jī)制(Node.js篇)》

為了證明我們的理論,把代碼改成下面的樣子:

setImmediate(() => {
  console.log("timer1")

  Promise.resolve().then(function () {
    console.log("promise1")
  })
})

setTimeout(() => {
  console.log("timer2")

  Promise.resolve().then(function () {
    console.log("promise2")
  })
}, 0)

Node輸出:
timer1               timer2
promise1    或者     promise2
timer2               timer1
promise2             promise1

按理說setTimeout(fn,0)應(yīng)該比setImmediate(fn)快,應(yīng)該只有第二種結(jié)果,為什么會出現(xiàn)兩種結(jié)果呢?
這是因?yàn)镹ode 做不到0毫秒,最少也需要1毫秒。實(shí)際執(zhí)行的時(shí)候,進(jìn)入事件循環(huán)以后,有可能到了1毫秒,也可能還沒到1毫秒,取決于系統(tǒng)當(dāng)時(shí)的狀況。如果沒到1毫秒,那么 timers 階段就會跳過,進(jìn)入 check 階段,先執(zhí)行setImmediate的回調(diào)函數(shù)。

另外,如果已經(jīng)過了Timer階段,那么setImmediate會比setTimeout更快,例如:

const fs = require("fs");

fs.readFile("test.js", () => {
  setTimeout(() => console.log(1));
  setImmediate(() => console.log(2));
});

上面代碼會先進(jìn)入 I/O callbacks 階段,然后是 check 階段,最后才是 timers 階段。因此,setImmediate才會早于setTimeout執(zhí)行。

具體可以看《Node 定時(shí)器詳解》。

5.2 不同異步任務(wù)執(zhí)行的快慢
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));

Promise.resolve().then(() => console.log(3));
process.nextTick(() => console.log(4));


輸出結(jié)果:4 3 1 2或者4 3 2 1

因?yàn)槲覀兩衔恼f過microTask會優(yōu)于macroTask運(yùn)行,所以先輸出下面兩個(gè),而在Node中process.nextTick比Promise更加優(yōu)先[3],所以4在3前。而根據(jù)我們之前所說的Node沒有絕對意義上的0ms,所以1,2的順序不固定。

5.3 MicroTask隊(duì)列與MacroTask隊(duì)列
   setTimeout(function () {
       console.log(1);
   },0);
   console.log(2);
   process.nextTick(() => {
       console.log(3);
   });
   new Promise(function (resolve, rejected) {
       console.log(4);
       resolve()
   }).then(res=>{
       console.log(5);
   })
   setImmediate(function () {
       console.log(6)
   })
   console.log("end");

Node輸出:
2 4 end 3 5 1 6

這個(gè)例子來源于《JavaScript中的執(zhí)行機(jī)制》。Promise的代碼是同步代碼,then和catch才是異步的,所以4要同步輸出,然后Promise的then位于microTask中,優(yōu)于其他位于macroTask隊(duì)列中的任務(wù),所以5會優(yōu)于1,6輸出,而Timer優(yōu)于Check階段,所以1,6。

6. 總結(jié)

綜上,關(guān)于最關(guān)鍵的順序,我們要依據(jù)以下幾條規(guī)則:

同一個(gè)上下文下,MicroTask會比MacroTask先運(yùn)行

然后瀏覽器按照一個(gè)MacroTask任務(wù),所有MicroTask的順序運(yùn)行,Node按照六個(gè)階段的順序運(yùn)行,并在每個(gè)階段后面都會運(yùn)行MicroTask隊(duì)列

同個(gè)MicroTask隊(duì)列下process.tick()會優(yōu)于Promise

Event loop還是比較深奧的,深入進(jìn)去會有很多有意思的東西,有任何問題還望不吝指出。

參考文檔:

《什么是瀏覽器的事件循環(huán)(Event Loop)》

《不要混淆nodejs和瀏覽器中的event loop》

《Node 定時(shí)器詳解》

《瀏覽器和Node不同的事件循環(huán)(Event Loop)》

《深入理解js事件循環(huán)機(jī)制(Node.js篇)》

《JavaScript中的執(zhí)行機(jī)制》

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/93543.html

相關(guān)文章

  • 簡潔明了探索覽器Event loop

    摘要:前段時(shí)間我對于瀏覽器中的和哪個(gè)先執(zhí)行有所困惑,苦于搜索也沒有發(fā)現(xiàn)很明確的答案,于是決定深入探索瀏覽器,現(xiàn)有所愚見,想與大家分享,希望能幫助到那些還在爬坑的人。瀏覽器端中的異步隊(duì)列有兩種隊(duì)列和隊(duì)列。瀏覽器會不斷從隊(duì)列中按順序取執(zhí)行。 前段時(shí)間我對于瀏覽器Event loop中的MacroTask和MicroTask哪個(gè)先執(zhí)行有所困惑,苦于搜索也沒有發(fā)現(xiàn)很明確的答案,于是決定深入探索瀏覽器...

    DrizzleX 評論0 收藏0
  • Javascript中的執(zhí)行機(jī)制——Event Loop

    摘要:由于兩個(gè)都是異步函數(shù),按照執(zhí)行順序,先將放到,接著將移到,因?yàn)樵谥付ㄒ牒蟛艌?zhí)行,所以先于到注冊回調(diào)函數(shù)到,所以輸出的結(jié)果是。 眾所周知,Javascript是單線程語言, 這就意味著,所有的任務(wù)都必須按照順序執(zhí)行,只有等前面的一個(gè)任務(wù)執(zhí)行完畢了,下一個(gè)任務(wù)才能執(zhí)行。如果前面一個(gè)任務(wù)耗時(shí)很長,后一個(gè)任務(wù)就得一直等著,因此,為了實(shí)現(xiàn)主線程的不阻塞,就有了Event Loop。 1、jav...

    aboutU 評論0 收藏0
  • 徹底弄懂Event Loop

    前言 我在學(xué)習(xí)瀏覽器和NodeJS的Event Loop時(shí)看了大量的文章,那些文章都寫的很好,但是往往是每篇文章有那么幾個(gè)關(guān)鍵的點(diǎn),很多篇文章湊在一起綜合來看,才可以對這些概念有較為深入的理解。 于是,我在看了大量文章之后,想要寫這么一篇博客,不采用官方的描述,結(jié)合自己的理解以及示例代碼,用最通俗的語言表達(dá)出來。希望大家可以通過這篇文章,了解到Event Loop到底是一種什么機(jī)制,瀏覽器和Nod...

    hersion 評論0 收藏0
  • 大話javascript 4期:事件循環(huán)(3)

    摘要:令人困惑的是,文檔中稱,指定的回調(diào)函數(shù),總是排在前面。另外,由于指定的回調(diào)函數(shù)是在本次事件循環(huán)觸發(fā),而指定的是在下次事件循環(huán)觸發(fā),所以很顯然,前者總是比后者發(fā)生得早,而且執(zhí)行效率也高因?yàn)椴挥脵z查任務(wù)隊(duì)列。 一、定時(shí)器 除了放置異步任務(wù)的事件,任務(wù)隊(duì)列還可以放置定時(shí)事件,即指定某些代碼在多少時(shí)間之后執(zhí)行。這叫做定時(shí)器(timer)功能,也就是定時(shí)執(zhí)行的代碼。 定時(shí)器功能主要由setTim...

    liujs 評論0 收藏0
  • Node.js Event Loop之Timers, process.nextTick()

    摘要:前言以異步和事件驅(qū)動的特性著稱但異步是怎么實(shí)現(xiàn)的呢其中核心的一部分就是下文中內(nèi)容基本來自于文檔有不準(zhǔn)確地方請指出什么是能讓的操作表現(xiàn)得無阻塞盡管是單線程的但通過盡可能的將操作放到操作系統(tǒng)內(nèi)核由于現(xiàn)在大多數(shù)內(nèi)核都是多線程的它們可以在后臺執(zhí)行多 前言 Node.js以異步I/O和事件驅(qū)動的特性著稱,但異步I/O是怎么實(shí)現(xiàn)的呢?其中核心的一部分就是event loop,下文中內(nèi)容基本來自于N...

    sarva 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<