對(duì)比瀏覽器eventLoop與node中的eventLoop

js中的單線程

為什么js會(huì)設(shè)計(jì)為單線程:

由于js運(yùn)行的環(huán)境(瀏覽器)決定的,反正多個(gè)線程同時(shí)操作dom,所以js被設(shè)計(jì)成單線程,這只是說js運(yùn)行的環(huán)境是單線的。如一些io操作,耗時(shí)操作,如果單純的單線程肯定是會(huì)阻塞ui,而js是非阻塞

事件循環(huán)(EventLoop)

由于js是單線的,所以在同一時(shí)間只能執(zhí)行一個(gè)任務(wù),如果這個(gè)任務(wù)是耗時(shí)操作,js是如何做到非阻塞的呢。
從類型上,任務(wù)可以分為兩種:同步任務(wù)和異步任務(wù)。

同步任務(wù)

但js引擎之執(zhí)行js代碼時(shí),碰到同步任務(wù)就是直接在主線程中執(zhí)行

異步任務(wù)

當(dāng)js引擎在執(zhí)行js代碼時(shí),碰到異步任務(wù),肯定是不會(huì)等它執(zhí)行完之后再執(zhí)行其他的任務(wù),此時(shí)是將這個(gè)異步任務(wù)放在callback queue中,當(dāng)主線程任務(wù)執(zhí)行完之后再去callback queue中取出任務(wù)執(zhí)行。不停的重復(fù)這個(gè)過程,這個(gè)循環(huán)其實(shí)就是我們一直所說的eventLoop

異步任務(wù)也分為兩種:微任務(wù)(micro task)和宏任務(wù)(macro task)

瀏覽器

1.js引擎執(zhí)行一塊代碼
2.執(zhí)行同步方法
3.碰到異步任務(wù)
4.根據(jù)不同的任務(wù)類型將其放在不同的隊(duì)列中(micro task 與macro task)
5.當(dāng)主線程代碼執(zhí)行完畢,再循環(huán)執(zhí)行micro task中的任務(wù)
6.當(dāng)micro task 隊(duì)列為空時(shí),會(huì)先沖macro task中取出一條放在micro task中,再去循環(huán)執(zhí)行micro task 中的任務(wù)
7.重復(fù)5,6知道m(xù)acro task與micro task 都為空

  • macro task
    setTimeout
    setInterval
    setImmediate
    I/O
  • micor task
    promise
    process.nextTick
    setImmediate
    MutationObserver

分析幾個(gè)簡(jiǎn)單的demo

setTimeout(function(){
    console.log('time1')
},0);
console.log('main')
//main
//time1
說明js執(zhí)行并不會(huì)阻塞,異步任務(wù)一定是在之后執(zhí)行的,time會(huì)放在macor task中

timeout與promise的順序

setTimeout(function(){
    console.log('time1')
},0);

Promise.resolve().then(function(){
    console.log('promise1')
})
console.log('main')
//main
//promise1
//time1
從log可以看出來,promise是先執(zhí)行的,如果time跟promise是同一個(gè)callback queue的話time應(yīng)該會(huì)先執(zhí)行,所以time跟promise是放在2個(gè)不同的隊(duì)列中

setTimeout(function(){
    console.log('time1')
},0);

Promise.resolve().then(function(){
    setTimeout(function(){
        console.log('time2')
    },0);
    Promise.resolve().then(function(){

        console.log('promise2')
    })
    console.log('promise1')
})
setTimeout(function(){
    console.log('time3')
},0);
console.log('main')
//main
//promise1
//promise2
//time1
//time3
//time2
如果在promise中再執(zhí)行一個(gè)time 與一個(gè)promise,分析首先js加載代碼,macor task隊(duì)列中是放了time1跟time3,micro task 中只有一個(gè)promise1方法,
我們?cè)趫?zhí)行micro task 中的promise1時(shí)候,又發(fā)現(xiàn)了2個(gè)異步任務(wù),他會(huì)重復(fù)這個(gè)操作,再次區(qū)分任務(wù)的類型 將time2放在macor task中,promise2放在,micor task中,然后執(zhí)行micro task 中的promise2,再去macro task取出time執(zhí)行

注意

在瀏覽器中,執(zhí)行優(yōu)先級(jí)為 同步代碼 > 微任務(wù) > 宏任務(wù),一次性執(zhí)行所有的micor task

node eventLoop

node的事件輪詢與瀏覽器的不太一樣,如圖

image.png

各個(gè)階段

node 的macro task更為復(fù)雜,microTask是串在task的各個(gè)階段執(zhí)行的。
當(dāng) Node 啟動(dòng)時(shí),回初始化 Event Loop ,每個(gè) Loop 都有六個(gè)階段

  • timers 階段:執(zhí)行 setTimeout、setInterval 的 callback 回調(diào)。
  • I/O callbacks階段:執(zhí)行除了 close 事件的 callbacks 、被timers(定時(shí)器,setTimeout、setInterval 等)設(shè)定的 callbacks 、setImmediate() 設(shè)定的 callbacks 之外的 callbacks
  • idle,prepare階段:node 內(nèi)部使用,Process.nextTick 在此階段執(zhí)行
  • poll 階段:獲取新的 I/O 事件, 適當(dāng)?shù)臈l件下 node 將阻塞在這里
  • check 階段:執(zhí)行 setImmediate() 的回調(diào)函數(shù)
  • close callbacks 階段:執(zhí)行 close 事件的 callback ,例如 socket.on('close', callback);
setImmediate(function immediate () {
  console.log('immediate');
});
setTimeout(function timeout () {
  console.log('timeout');
},0);

setImmediate
setTimeout

setTimeout
setImmediate
  • 輸出不固定
    setTimeout/setInterval 的第二個(gè)參數(shù)取值范圍是:[1, 2^31 - 1],如果超過這個(gè)范圍則會(huì)初始化為 1,即 setTimeout(fn, 0) === setTimeout(fn, 1)。
  • setTimeout 的回調(diào)函數(shù)在 timer 階段執(zhí)行
  • setImmediate的回掉在check階段
    1.timer 前的準(zhǔn)備時(shí)間超過 1ms,滿足 loop->time >= 1,即time函數(shù)已經(jīng)注冊(cè),times階段事件隊(duì)列不為空,則執(zhí)行 timer 階段(setTimeout)的回調(diào)函數(shù)
    2.timer 前的準(zhǔn)備時(shí)間小于 1ms,即time函數(shù)未注冊(cè)完成,times階段事件隊(duì)列為空,就會(huì)進(jìn)入下一個(gè)階段,則先執(zhí)行 check 階段(setImmediate)的回調(diào)函數(shù),下一次 event loop 執(zhí)行 timer 階段(setTimeout)的回調(diào)函數(shù)
setImmediate(function immediate () {
  console.log('immediate');
});
setTimeout(function timeout () {
  console.log('timeout');
},0);

var data = Date.now();
while(Date.now() - data < 2) {

}

// 結(jié)果一定是 
timeout
immediate
const fs = require('fs')
fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('setTimeout')
  }, 0)

  setImmediate(() => {
    console.log('setImmediate')
  })
})

//
immediate
timeout
  • fs 的I/O callback 執(zhí)行完成 ,進(jìn)入poll 階段
  • poll階段有兩個(gè)主要功能
    1. 執(zhí)行以及到時(shí)間的定時(shí)器
    2. 處理輪詢隊(duì)列中的事件
  • 此時(shí)poll為空,進(jìn)入check階段
  • check執(zhí)行setImmediate()
  • 進(jìn)入close callbacks 再進(jìn)入timer
  • 所以setImmediate先執(zhí)行

node里面的微任務(wù)

process.nextTick ,promise在各個(gè)階段切換的中間執(zhí)行,即從一個(gè)階段切換到下個(gè)階段前執(zhí)行

var fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('setTimeout');
  }, 0);
  setImmediate(() => {
    console.log('setImmediate');
    process.nextTick(()=>{
      console.log('nextTick3');
    })
  });
  process.nextTick(()=>{
    console.log('nextTick1');
  })
  process.nextTick(()=>{
    console.log('nextTick2');
  })
});
//
nextTick1
nextTick2
setImmediate
nextTick3
setTimeout
  • 此時(shí)執(zhí)行的順序是 I/O -->nextTick1-->nextTick2-->poll-->check-->nextTick3-->close-->timers
setTimeout(function () {
  console.log('execute in first timeout');
  Promise.resolve(3).then(res => {
    console.log('execute in third promise');
  });
}, 0);
setTimeout(function () {
  console.log('execute in second timeout');
  Promise.resolve(4).then(res => {
    console.log('execute in fourth promise');
  });
}, 0);
Promise.resolve(1).then(res => {
  console.log('execute in first promise');
});
Promise.resolve(2).then(res => {
  console.log('execute in second promise');
});

node 結(jié)果

execute in first promise
execute in second promise
execute in first timeout        
execute in second timeout     // node 先執(zhí)行
execute in third promise
execute in fourth promise

瀏覽器結(jié)果

execute in first promise
execute in second promise
execute in first timeout
execute in third promise
execute in second timeout
execute in fourth promise
  • 很明顯,在不同的環(huán)境 js eventLoop得到不一樣的結(jié)果
  • 基于瀏覽器的eventLoop我們很容易得出下面的結(jié)果
  • 基于node:
    1.promise也就是micor task 在下個(gè)階段之前被調(diào)用
    2.當(dāng)處于timers階段時(shí),會(huì)將對(duì)應(yīng)的timer隊(duì)列處理完,故 2個(gè)timeout會(huì)先被執(zhí)行,再會(huì)進(jìn)入下面一個(gè)狀態(tài),此時(shí)才會(huì)調(diào)用promise
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在上一篇文章 從進(jìn)程和線程了解瀏覽器的工作原理 中,我們已經(jīng)了解了瀏覽器的渲染流程,瀏覽器初次渲染完成后,接下來就...
    zouyang0921閱讀 701評(píng)論 0 1
  • 最近比較忙,硬是沒抽出時(shí)間總結(jié)一點(diǎn)小知識(shí)。最近在網(wǎng)上看到一篇不錯(cuò)的文章,轉(zhuǎn)一下!本文的目的就是要保證你徹底弄懂ja...
    殖民_FE閱讀 329評(píng)論 1 2
  • 首先需要明確的一點(diǎn)兒是ECMAScript中沒有event loop,event loop是在HTML Stand...
    luckySnail閱讀 1,174評(píng)論 0 0
  • 剛開始使用JS異步的時(shí)候,有這樣的疑問:JS不是單線程的嗎?為什么會(huì)有異步機(jī)制?但是如果沒有異步機(jī)制,定時(shí)器又是怎...
    盛夏晚清風(fēng)閱讀 1,725評(píng)論 0 11
  • 每個(gè)生命都是一朵花,都值得我們用心灌溉,讓生命的花朵綻放得更絢爛。無論這朵花是牡丹還是蒲公英,都值得被尊重,因?yàn)槊?..
    老孫的樹洞閱讀 2,902評(píng)論 0 5

友情鏈接更多精彩內(nèi)容