国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

帶你徹底弄懂Event Loop

hersion / 814人閱讀

前言

我在學習瀏覽器和NodeJS的Event Loop時看了大量的文章,那些文章都寫的很好,但是往往是每篇文章有那么幾個關鍵的點,很多篇文章湊在一起綜合來看,才可以對這些概念有較為深入的理解。

于是,我在看了大量文章之后,想要寫這么一篇博客,不采用官方的描述,結合自己的理解以及示例代碼,用最通俗的語言表達出來。希望大家可以通過這篇文章,了解到Event Loop到底是一種什么機制,瀏覽器和NodeJS的Event Loop又有什么區別。如果在文中出現書寫錯誤的地方,歡迎大家留言一起探討。

(PS:說到Event Loop肯定會提到Promise,我根據Promise A+規范自己實現了一個簡易Promise庫,源碼放到Github上,大家有需要的可以當做參考,后續我也會也寫一篇博客來講Promise,如果對你有用,就請給個Star吧~)

正文 Event Loop是什么

event loop是一個執行模型,在不同的地方有不同的實現。瀏覽器和NodeJS基于不同的技術實現了各自的Event Loop。

瀏覽器的Event Loop是在html5的規范中明確定義。

NodeJS的Event Loop是基于libuv實現的。可以參考Node的官方文檔以及libuv的官方文檔。

libuv已經對Event Loop做出了實現,而HTML5規范中只是定義了瀏覽器中Event Loop的模型,具體的實現留給了瀏覽器廠商。

宏隊列和微隊列

宏隊列,macrotask,也叫tasks。 一些異步任務的回調會依次進入macro task queue,等待后續被調用,這些異步任務包括:

setTimeout

setInterval

setImmediate (Node獨有)

requestAnimationFrame (瀏覽器獨有)

I/O

UI rendering (瀏覽器獨有)

微隊列,microtask,也叫jobs。 另一些異步任務的回調會依次進入micro task queue,等待后續被調用,這些異步任務包括:

process.nextTick (Node獨有)

Promise

Object.observe

MutationObserver

(注:這里只針對瀏覽器和NodeJS)

瀏覽器的Event Loop

我們先來看一張圖,再看完這篇文章后,請返回來再仔細看一下這張圖,相信你會有更深的理解。

這張圖將瀏覽器的Event Loop完整的描述了出來,我來講執行一個JavaScript代碼的具體流程:

執行全局Script同步代碼,這些同步代碼有一些是同步語句,有一些是異步語句(比如setTimeout等);

全局Script代碼執行完畢后,調用棧Stack會清空;

從微隊列microtask queue中取出位于隊首的回調任務,放入調用棧Stack中執行,執行完后microtask queue長度減1;

繼續取出位于隊首的任務,放入調用棧Stack中執行,以此類推,直到直到把microtask queue中的所有任務都執行完畢。注意,如果在執行microtask的過程中,又產生了microtask,那么會加入到隊列的末尾,也會在這個周期被調用執行

microtask queue中的所有任務都執行完畢,此時microtask queue為空隊列,調用棧Stack也為空;

取出宏隊列macrotask queue中位于隊首的任務,放入Stack中執行;

執行完畢后,調用棧Stack為空;

重復第3-7個步驟;

重復第3-7個步驟;

......

可以看到,這就是瀏覽器的事件循環Event Loop

這里歸納3個重點:

宏隊列macrotask一次只從隊列中取一個任務執行,執行完后就去執行微任務隊列中的任務;

微任務隊列中所有的任務都會被依次取出來執行,知道microtask queue為空;

圖中沒有畫UI rendering的節點,因為這個是由瀏覽器自行判斷決定的,但是只要執行UI rendering,它的節點是在執行完所有的microtask之后,下一個macrotask之前,緊跟著執行UI render。

好了,概念性的東西就這么多,來看幾個示例代碼,測試一下你是否掌握了:

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {
  console.log(6);
})

console.log(7);

這里結果會是什么呢?運用上面了解到的知識,先自己做一下試試看。

// 正確答案
1
4
7
5
2
3
6

你答對了嗎?

我們來分析一下整個流程:

執行全局Script代碼

Step 1

console.log(1)

Stack Queue: [console]

Macrotask Queue: []

Microtask Queue: []

打印結果:     
1

Step 2

setTimeout(() => {
  // 這個回調函數叫做callback1,setTimeout屬于macrotask,所以放到macrotask queue中
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

Stack Queue: [setTimeout]

Macrotask Queue: [callback1]

Microtask Queue: []

打印結果:     
1

Step 3

new Promise((resolve, reject) => {
  // 注意,這里是同步執行的,如果不太清楚,可以去看一下我開頭自己實現的promise啦~~
  console.log(4)
  resolve(5)
}).then((data) => {
  // 這個回調函數叫做callback2,promise屬于microtask,所以放到microtask queue中
  console.log(data);
})

Stack Queue: [promise]

Macrotask Queue: [callback1]

Microtask Queue: [callback2]

打印結果:     
1
4

Step 5

setTimeout(() => {
  // 這個回調函數叫做callback3,setTimeout屬于macrotask,所以放到macrotask queue中
  console.log(6);
})

Stack Queue: [setTimeout]

Macrotask Queue: [callback1, callback3]

Microtask Queue: [callback2]

打印結果:     
1
4

Step 6

console.log(7)

Stack Queue: [console]

Macrotask Queue: [callback1, callback3]

Microtask Queue: [callback2]

打印結果:     
1
4
7

好啦,全局Script代碼執行完了,進入下一個步驟,從microtask queue中依次取出任務執行,直到microtask queue隊列為空。

Step 7

console.log(data)       // 這里data是Promise的決議值5

Stack Queue: [callback2]

Macrotask Queue: [callback1, callback3]

Microtask Queue: []

打印結果:     
1
4
7
5

這里microtask queue中只有一個任務,執行完后開始從宏任務隊列macrotask queue中取位于隊首的任務執行

Step 8

console.log(2)

Stack Queue: [callback1]

Macrotask Queue: [callback3]

Microtask Queue: []

打印結果:     
1
4
7
5
2

但是,執行callback1的時候又遇到了另一個Promise,Promise異步執行完后在microtask queue中又注冊了一個callback4回調函數

Step 9

Promise.resolve().then(() => {
  // 這個回調函數叫做callback4,promise屬于microtask,所以放到microtask queue中
  console.log(3)
});

Stack Queue: [promise]

Macrotask v: [callback3]

Microtask Queue: [callback4]

打印結果:     
1
4
7
5
2

取出一個宏任務macrotask執行完畢,然后再去微任務隊列microtask queue中依次取出執行

Step 10

console.log(3)

Stack Queue: [callback4]

Macrotask Queue: [callback3]

Microtask Queue: []

打印結果:     
1
4
7
5
2
3

微任務隊列全部執行完,再去宏任務隊列中取第一個任務執行

Step 11

console.log(6)

Stack Queue: [callback3]

Macrotask Queue: []

Microtask Queue: []

打印結果:     
1
4
7
5
2
3
6

以上,全部執行完后,Stack Queue為空,Macrotask Queue為空,Micro Queue為空

Stack Queue: []

Macrotask Queue: []

Microtask Queue: []

最終打印結果:     
1
4
7
5
2
3
6

因為是第一個例子,所以這里分析的比較詳細,大家仔細看一下,接下來我們再來一個例子:

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
  
  Promise.resolve().then(() => {
    console.log(6)
  }).then(() => {
    console.log(7)
    
    setTimeout(() => {
      console.log(8)
    }, 0);
  });
})

setTimeout(() => {
  console.log(9);
})

console.log(10);

最終輸出結果是什么呢?參考前面的例子,好好想一想......

// 正確答案
1
4
10
5
6
7
2
3
9
8

相信大家都答對了,這里的關鍵在前面已經提過:

在執行微隊列microtask queue中任務的時候,如果又產生了microtask,那么會繼續添加到隊列的末尾,也會在這個周期執行,直到microtask queue為空停止。

注:當然如果你在microtask中不斷的產生microtask,那么其他宏任務macrotask就無法執行了,但是這個操作也不是無限的,拿NodeJS中的微任務process.nextTick()來說,它的上限是1000個,后面我們會講到。

瀏覽器的Event Loop就說到這里,下面我們看一下NodeJS中的Event Loop,它更復雜一些,機制也不太一樣。

NodeJS中的Event Loop libuv

先來看一張libuv的結構圖:

NodeJS中的宏隊列和微隊列

NodeJS的Event Loop中,執行宏隊列的回調任務有6個階段,如下圖:

各個階段執行的任務如下:

timers階段:這個階段執行setTimeout和setInterval預定的callback

I/O callback階段:執行除了close事件的callbacks、被timers設定的callbacks、setImmediate()設定的callbacks這些之外的callbacks

idle, prepare階段:僅node內部使用

poll階段:獲取新的I/O事件,適當的條件下node將阻塞在這里

check階段:執行setImmediate()設定的callbacks

close callbacks階段:執行socket.on("close", ....)這些callbacks

NodeJS中宏隊列主要有4個

由上面的介紹可以看到,回調事件主要位于4個macrotask queue中:

Timers Queue

IO Callbacks Queue

Check Queue

Close Callbacks Queue

這4個都屬于宏隊列,但是在瀏覽器中,可以認為只有一個宏隊列,所有的macrotask都會被加到這一個宏隊列中,但是在NodeJS中,不同的macrotask會被放置在不同的宏隊列中。

NodeJS中微隊列主要有2個

Next Tick Queue:是放置process.nextTick(callback)的回調任務的

Other Micro Queue:放置其他microtask,比如Promise等

在瀏覽器中,也可以認為只有一個微隊列,所有的microtask都會被加到這一個微隊列中,但是在NodeJS中,不同的microtask會被放置在不同的微隊列中。

具體可以通過下圖加深一下理解:

大體解釋一下NodeJS的Event Loop過程:

執行全局Script的同步代碼

執行microtask微任務,先執行所有Next Tick Queue中的所有任務,再執行Other Microtask Queue中的所有任務

開始執行macrotask宏任務,共6個階段,從第1個階段開始執行相應每一個階段macrotask中的所有任務,注意,這里是所有每個階段宏任務隊列的所有任務,在瀏覽器的Event Loop中是只取宏隊列的第一個任務出來執行,每一個階段的macrotask任務執行完畢后,開始執行微任務,也就是步驟2

Timers Queue -> 步驟2 -> I/O Queue -> 步驟2 -> Check Queue -> 步驟2 -> Close Callback Queue -> 步驟2 -> Timers Queue ......

這就是Node的Event Loop

關于NodeJS的macrotask queue和microtask queue,我畫了兩張圖,大家作為參考:

好啦,概念理解了我們通過幾個例子來實戰一下:

第一個例子

console.log("start");

setTimeout(() => {          // callback1
  console.log(111);
  setTimeout(() => {        // callback2
    console.log(222);
  }, 0);
  setImmediate(() => {      // callback3
    console.log(333);
  })
  process.nextTick(() => {  // callback4
    console.log(444);  
  })
}, 0);

setImmediate(() => {        // callback5
  console.log(555);
  process.nextTick(() => {  // callback6
    console.log(666);  
  })
})

setTimeout(() => {          // callback7              
  console.log(777);
  process.nextTick(() => {  // callback8
    console.log(888);   
  })
}, 0);

process.nextTick(() => {    // callback9
  console.log(999);  
})

console.log("end");

請運用前面學到的知識,仔細分析一下......

// 正確答案
start
end
999
111
777
444
888
555
333
666
222
更新 2018.9.20

上面這段代碼你執行的結果可能會有多種情況,原因解釋如下。

setTimeout(fn, 0)不是嚴格的0,一般是setTimeout(fn, 3)或什么,會有一定的延遲時間,當setTimeout(fn, 0)和setImmediate(fn)出現在同一段同步代碼中時,就會存在兩種情況。

第1種情況:同步代碼執行完了,Timer還沒到期,setImmediate回調先注冊到Check Queue中,開始執行微隊列,然后是宏隊列,先從Timers Queue中開始,發現沒回調,往下走直到Check Queue中有回調,執行,然后timer到期(只要在執行完Timer Queue后到期效果就都一樣),timer回調注冊到Timers Queue中,下一輪循環執行到Timers Queue中才能執行那個timer 回調;所以,這種情況下,setImmediate(fn)回調先于setTimeout(fn, 0)回調執行

第2種情況:同步代碼還沒執行完,timer先到期,timer回調先注冊到Timers Queue中,執行到setImmediate了,它的回調再注冊到Check Queue中。 然后,同步代碼執行完了,執行微隊列,然后開始先執行Timers Queue,先執行Timer 回調,再到Check Queue,執行setImmediate回調;所以,這種情況下,setTimeout(fn, 0)回調先于setImmediate(fn)回調執行

所以,在同步代碼中同時調setTimeout(fn, 0)和setImmediate情況是不確定的,但是如果把他們放在一個IO的回調,比如readFile("xx", function () {// ....})回調中,那么IO回調是在IO Queue中,setTimeout到期回調注冊到Timers Queue,setImmediate回調注冊到Check Queue,IO Queue執行完到Check Queue,timer Queue得到下個周期,所以setImmediate回調這種情況下肯定比setTimeout(fn, 0)回調先執行。

綜上,這個例子是不太好的,setTimeout(fn, 0)和setImmediate(fn)如果想要保證結果唯一,就放在一個IO Callback中吧,上面那段代碼可以把所有它倆同步執行的代碼都放在一個IO Callback中,結果就唯一了。

更新結束

你答對了嗎?我們來一起分析一下:

執行全局Script代碼,先打印start,向下執行,將setTimeout的回調callback1注冊到Timers Queue中,再向下執行,將setImmediate的回調callback5注冊到Check Queue中,接著向下執行,將setTimeout的回調callback7注冊到Timers Queue中,繼續向下,將process.nextTick的回調callback9注冊到微隊列Next Tick Queue中,最后一步打印end。此時,各個隊列的回調情況如下:

宏隊列

Timers Queue: [callback1, callback7]

Check Queue: [callback5]

IO Callback Queue: []

Close Callback Queue: []

微隊列

Next Tick Queue: [callback9]

Other Microtask Queue: []

打印結果    
start
end

全局Script執行完了,開始依次執行微任務Next Tick Queue中的全部回調任務。此時Next Tick Queue中只有一個callback9,將其取出放入調用棧中執行,打印999

宏隊列

Timers Queue: [callback1, callback7]

Check Queue: [callback5]

IO Callback Queue: []

Close Callback Queue: []

微隊列

Next Tick Queue: []

Other Microtask Queue: []

打印結果    
start
end
999

開始依次執行6個階段各自宏隊列中的所有任務,先執行第1個階段Timers Queue中的所有任務,先取出callback1執行,打印111,callback1函數繼續向下,依次把callback2放入Timers Queue中,把callback3放入Check Queue中,把callback4放入Next Tick Queue中,然后callback1執行完畢。再取出Timers Queue中此時排在首位的callback7執行,打印777,把callback8放入Next Tick Queue中,執行完畢。此時,各隊列情況如下:

宏隊列

Timers Queue: [callback2]

Check Queue: [callback5, callback3]

IO Callback Queue: []

Close Callback Queue: []

微隊列

Next Tick Queue: [callback4, callback8]

Other Microtask Queue: []

打印結果    
start
end
999
111
777

6個階段每階段的宏任務隊列執行完畢后,都會開始執行微任務,此時,先取出Next Tick Queue中的所有任務執行,callback4開始執行,打印444,然后callback8開始執行,打印888,Next Tick Queue執行完畢,開始執行Other Microtask Queue中的任務,因為里面為空,所以繼續向下。

宏隊列

Timers Queue: [callback2]

Check Queue: [callback5, callback3]

IO Callback Queue: []

Close Callback Queue: []

微隊列

Next Tick Queue: []

Other Microtask Queue: []

打印結果    
start
end
999
111
777
444
888

第2個階段IO Callback Queue隊列為空,跳過,第3和第4個階段一般是Node內部使用,跳過,進入第5個階段Check Queue。取出callback5執行,打印555,把callback6放入Next Tick Queue中,執行callback3,打印333

宏隊列

Timers Queue: [callback2]

Check Queue: []

IO Callback Queue: []

Close Callback Queue: []

微隊列

Next Tick Queue: [callback6]

Other Microtask Queue: []

打印結果    
start
end
999
111
777
444
888
555
333

執行微任務隊列,先執行Next Tick Queue,取出callback6執行,打印666,執行完畢,因為Other Microtask Queue為空,跳過。

宏隊列

Timers Queue: [callback2]

Check Queue: []

IO Callback Queue: []

Close Callback Queue: []

微隊列

Next Tick Queue: [callback6]

Other Microtask Queue: []

打印結果    
start
end
999
111
777
444
888
555
333

執行第6個階段Close Callback Queue中的任務,為空,跳過,好了,此時一個循環已經結束。進入下一個循環,執行第1個階段Timers Queue中的所有任務,取出callback2執行,打印222,完畢。此時,所有隊列包括宏任務隊列和微任務隊列都為空,不再打印任何東西。

宏隊列

Timers Queue: []

Check Queue: []

IO Callback Queue: []

Close Callback Queue: []

微隊列

Next Tick Queue: [callback6]

Other Microtask Queue: []

最終結果    
start
end
999
111
777
444
888
555
333
666
222

以上就是這道題目的詳細分析,如果沒有明白,一定要多看幾次。

下面引入Promise再來看一個例子:

console.log("1");

setTimeout(function() {
    console.log("2");
    process.nextTick(function() {
        console.log("3");
    })
    new Promise(function(resolve) {
        console.log("4");
        resolve();
    }).then(function() {
        console.log("5")
    })
})

new Promise(function(resolve) {
    console.log("7");
    resolve();
}).then(function() {
    console.log("8")
})
process.nextTick(function() {
  console.log("6");
})

setTimeout(function() {
    console.log("9");
    process.nextTick(function() {
        console.log("10");
    })
    new Promise(function(resolve) {
        console.log("11");
        resolve();
    }).then(function() {
        console.log("12")
    })
})

大家仔細分析,相比于上一個例子,這里由于存在Promise,所以Other Microtask Queue中也會有回調任務的存在,執行到微任務階段時,先執行Next Tick Queue中的所有任務,再執行Other Microtask Queue中的所有任務,然后才會進入下一個階段的宏任務。明白了這一點,相信大家都可以分析出來,下面直接給出正確答案,如有疑問,歡迎留言和我討論。

// 正確答案
1
7
6
8
2
4
9
11
3
10
5
12
setTimeout 對比 setImmediate

setTimeout(fn, 0)在Timers階段執行,并且是在poll階段進行判斷是否達到指定的timer時間才會執行

setImmediate(fn)在Check階段執行

兩者的執行順序要根據當前的執行環境才能確定:

如果兩者都在主模塊(main module)調用,那么執行先后取決于進程性能,順序隨機

如果兩者都不在主模塊調用,即在一個I/O Circle中調用,那么setImmediate的回調永遠先執行,因為會先到Check階段

setImmediate 對比 process.nextTick

setImmediate(fn)的回調任務會插入到宏隊列Check Queue中

process.nextTick(fn)的回調任務會插入到微隊列Next Tick Queue中

process.nextTick(fn)調用深度有限制,上限是1000,而setImmedaite則沒有

總結

瀏覽器的Event Loop和NodeJS的Event Loop是不同的,實現機制也不一樣,不要混為一談。

瀏覽器可以理解成只有1個宏任務隊列和1個微任務隊列,先執行全局Script代碼,執行完同步代碼調用棧清空后,從微任務隊列中依次取出所有的任務放入調用棧執行,微任務隊列清空后,從宏任務隊列中只取位于隊首的任務放入調用棧執行,注意這里和Node的區別,只取一個,然后繼續執行微隊列中的所有任務,再去宏隊列取一個,以此構成事件循環。

NodeJS可以理解成有4個宏任務隊列和2個微任務隊列,但是執行宏任務時有6個階段。先執行全局Script代碼,執行完同步代碼調用棧清空后,先從微任務隊列Next Tick Queue中依次取出所有的任務放入調用棧中執行,再從微任務隊列Other Microtask Queue中依次取出所有的任務放入調用棧中執行。然后開始宏任務的6個階段,每個階段都將該宏任務隊列中的所有任務都取出來執行(注意,這里和瀏覽器不一樣,瀏覽器只取一個),每個宏任務階段執行完畢后,開始執行微任務,再開始執行下一階段宏任務,以此構成事件循環。

MacroTask包括: setTimeout、setInterval、 setImmediate(Node)、requestAnimation(瀏覽器)、IO、UI rendering

Microtask包括: process.nextTick(Node)、Promise、Object.observe、MutationObserver

歡迎關注我的公眾號

參考鏈接

不要混淆nodejs和瀏覽器中的event loop
node中的Event模塊
Promises, process.nextTick And setImmediate
瀏覽器和Node不同的事件循環
Tasks, microtasks, queues and schedules
理解事件循環淺析

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97392.html

相關文章

  • 優秀文章收藏(慢慢消化)持續更新~

    摘要:整理收藏一些優秀的文章及大佬博客留著慢慢學習原文協作規范中文技術文檔協作規范阮一峰編程風格凹凸實驗室前端代碼規范風格指南這一次,徹底弄懂執行機制一次弄懂徹底解決此類面試問題瀏覽器與的事件循環有何區別筆試題事件循環機制異步編程理解的異步 better-learning 整理收藏一些優秀的文章及大佬博客留著慢慢學習 原文:https://www.ahwgs.cn/youxiuwenzhan...

    JeOam 評論0 收藏0
  • 這一次,徹底弄懂 JavaScript 執行機制

    摘要:事件完成,回調函數進入。主線程從讀取回調函數并執行。終于執行完了,終于從進入了主線程執行。遇到,立即執行。宏任務微任務第三輪事件循環宏任務執行結束,執行兩個微任務和。事件循環事件循環是實現異步的一種方法,也是的執行機制。 本文的目的就是要保證你徹底弄懂javascript的執行機制,如果讀完本文還不懂,可以揍我。不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發工作...

    dreambei 評論0 收藏0
  • 徹底弄懂 JavaScript 執行機制

    摘要:關于這部分有嚴格的文字定義,但本文的目的是用最小的學習成本徹底弄懂執行機制,所以同步和異步任務分別進入不同的執行場所,同步的進入主線程,異步的進入并注冊函數。宏任務微任務第三輪事件循環宏任務執行結束,執行兩個微任務和。 不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發工作,我們經常會遇到這樣的情況:給定的幾行代碼,我們需要知道其輸出內容和順序。 因為javascr...

    gyl_coder 評論0 收藏0
  • 前端進階系列(八):JS執行機制

    摘要:一直以來,對的執行機制都是模棱兩可,知道今天看了文章這一次,徹底弄懂執行機制和的規范和實現,才對的執行機制有了深入的理解,下面是我的學習總結。個要點是單線程語言是的執行機制,為了實現主線程的不阻塞,就這么誕生了。 一直以來,對JS的執行機制都是模棱兩可,知道今天看了文章—《這一次,徹底弄懂JavaScript執行機制》和《Event Loop的規范和實現》,才對JS的執行機制有了深入的...

    JackJiang 評論0 收藏0
  • 前端基礎

    摘要:談起閉包,它可是兩個核心技術之一異步基于打造前端持續集成開發環境本文將以一個標準的項目為例,完全拋棄傳統的前端項目開發部署方式,基于容器技術打造一個精簡的前端持續集成的開發環境。 這一次,徹底弄懂 JavaScript 執行機制 本文的目的就是要保證你徹底弄懂javascript的執行機制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥,不論是面試求職,還是日...

    graf 評論0 收藏0

發表評論

0條評論

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