欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS事件循環(huán)-微任務(wù)-宏任務(wù)(原理講解+面試題分析)

 更新時間:2023年01月08日 15:00:48   作者:既白biu  
這篇文章主要介紹了JS事件循環(huán)-微任務(wù)-宏任務(wù)的原理,本文章含有面試題分析,不管是面試者還是想要學(xué)習(xí)相關(guān)內(nèi)容的都可以很好的理解、掌握這部分內(nèi)容,需要的朋友可以參考下

前言

JS代碼在運行時,有兩種運行環(huán)境。

一是在瀏覽器中,二是在node中。

由于JS線程是單線程,在運行JS代碼時,可能會遇到比較耗時的操作,比如setTimeout,或者是發(fā)送網(wǎng)絡(luò)請求等,又由于JS線程是單線程,如果在解析耗時的代碼時候,停在了這里,那執(zhí)行代碼的性能將是比較低的。

為了解決此問題,在瀏覽器、node環(huán)境下,其實是有事件循環(huán)機制的。

瀏覽器的事件循環(huán)

瀏覽器的事件循環(huán)

JS線程執(zhí)行代碼時候,遇到比較耗時的操作時,將這些操作交給瀏覽器去處理,然后這些操作會根據(jù)不同的種類放進微任務(wù)隊列或者宏任務(wù)隊列,宏任務(wù)隊列和微任務(wù)隊列都不為空的時候,只有等微任務(wù)隊列為空,即微任務(wù)隊列里面的事件全部都執(zhí)行完之后,才會再去讓宏任務(wù)隊列中的事件出棧,之后交由JS線程去處理,執(zhí)行代碼。

事件循環(huán)大概就是如圖所示的流程:

瀏覽器的宏任務(wù)、微任務(wù)

其實,在瀏覽器拿到那些有些不能同步處理的事件的時候,有的會加入宏任務(wù)隊列,有的會加入微任務(wù)隊列,那么一般我們?nèi)绾螀^(qū)分呢?

一般情況下:加入宏任務(wù)隊列和微任務(wù)隊列的事件如下:

宏任務(wù)隊列(macrotask queue):ajax、setTimeout、setInterval、DOM監(jiān)聽、UI Rendering等

微任務(wù)隊列(microtask queue):Promise的then回調(diào)、 Mutation Observer API、queueMicrotask()。

那么這些事件的執(zhí)行順序是怎么樣子的呢?

首先,有一個原則,宏任務(wù)隊列里面的事件,要執(zhí)行的話,一定是在確保微任務(wù)隊列為空的情況下,即微任務(wù)隊列里面的事件全部執(zhí)行完的情況。

其次,main script里面的內(nèi)容是最先執(zhí)行的。

由此,可以得到執(zhí)行順序為:main script > 微任務(wù)隊列里面的事件 > 宏任務(wù)里面的事件。

面試題一

題目如下:

setTimeout(function () {
  console.log("setTimeout1");
  new Promise(function (resolve) {
    resolve();
  }).then(function () {
    new Promise(function (resolve) {
      resolve();
    }).then(function () {
      console.log("then4");
    });
    console.log("then2");
  });
});

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

setTimeout(function () {
  console.log("setTimeout2");
});

console.log(2);

queueMicrotask(() => {
  console.log("queueMicrotask1")
});

new Promise(function (resolve) {
  resolve();
}).then(function () {
  console.log("then3");
});

// promise1
// 2
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2

分析如下:

在第一個事件循環(huán)里面,main script、宏任務(wù)、微任務(wù)里面的事件如下:

在判斷加入宏任務(wù)隊列還是微任務(wù)隊列時候,遵循如下原則:

宏任務(wù)隊列(macrotask queue):ajax、setTimeout、setInterval、DOM監(jiān)聽、UI Rendering等
微任務(wù)隊列(microtask queue):Promise的then回調(diào)、 Mutation Observer
API、queueMicrotask()。

按照這個原則,第一輪事件循環(huán)里面的事件如下:

先執(zhí)行main script、然后微任務(wù)隊列里面的,最后是宏任務(wù)隊列里面的

// promise1
// 2
// then1
// queueMicrotask1
// then3

之后執(zhí)行setTimeout1的宏任務(wù),此時第二輪事件循環(huán)里面的內(nèi)容如下:

第二輪事件循環(huán)執(zhí)行內(nèi)容如下:

// setTimeout1
// then2
// then4
// setTimeout2

綜上:最后執(zhí)行結(jié)果為:

// promise1
// 2
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2

面試題二

題目如下:

// async function bar() {
//   console.log("22222")
//   return new Promise((resolve) => {
//     resolve()
//   })
// }

// async function foo() {
//   console.log("111111")

//   await bar()

//   console.log("33333")
// }

// foo()
// console.log("444444")


async function async1 () {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}

async function async2 () {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('setTimeout')
}, 0)
 
async1();
 
new Promise (function (resolve) {
  console.log('promise1')
  resolve();
}).then (function () {
  console.log('promise2')
})

console.log('script end')

// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

第一輪事件循環(huán)里面的事件如下:

然后按照順序執(zhí)行,最后結(jié)果如下:

// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

node的事件循環(huán)

node的事件循環(huán)

瀏覽器的事件循環(huán)是是根據(jù)HTML5定義的規(guī)范來實現(xiàn)的,不同的瀏覽器可能會有不同的實現(xiàn),而Node中是由libuv實現(xiàn)的。

首先我們看一下node的架構(gòu)圖:

我們可以從圖中大致看出,事件循環(huán)是在libuv中實現(xiàn)的,libuv主要維護的是一個事件循環(huán)(Event Loop)和 線程池(worker threads)。

libuv是一個多平臺的專注于異步IO的庫,它最初是為Node開發(fā)的,但是現(xiàn)在也被使用到Luvit、Julia、pyuv等其他地方;

EventLoop負(fù)責(zé)調(diào)用系統(tǒng)的一些其他操作:文件的IO、Network、child-processes等

由圖可以看出,事件循環(huán)就像是一個橋梁,是連接著應(yīng)用程序的JavaScript(左邊部分)和系統(tǒng)調(diào)用(右邊線程池部分)之間的通道:

無論是我們的文件IO、數(shù)據(jù)庫、網(wǎng)絡(luò)IO、定時器、子進程,在完成對應(yīng)的操作后,都會將對應(yīng)的結(jié)果和回調(diào)函數(shù)放到事件循環(huán)(任務(wù)隊列)中;

事件循環(huán)會不斷的從任務(wù)隊列中取出對應(yīng)的事件(回調(diào)函數(shù))來執(zhí)行;

但是一次完整的事件循環(huán)Tick分成很多個階段:

  1. 定時器(Timers):本階段執(zhí)行已經(jīng)被 setTimeout() 和 setInterval() 的調(diào)度回調(diào)函數(shù)。
  2. 待定回調(diào)(Pending Callback):對某些系統(tǒng)操作(如TCP錯誤類型)執(zhí)行回調(diào),比如TCP連接時接收到ECONNREFUSED。idle, prepare:僅系統(tǒng)內(nèi)部使用。
  3. 輪詢(Poll):檢索新的 I/O 事件;執(zhí)行與 I/O 相關(guān)的回調(diào);
  4. 檢測(check):setImmediate() 回調(diào)函數(shù)在這里執(zhí)行。
  5. 關(guān)閉的回調(diào)函數(shù):一些關(guān)閉的回調(diào)函數(shù),如:socket.on(‘close’, …)

node的宏任務(wù)、微任務(wù)

node中也有微任務(wù)和宏任務(wù),執(zhí)行的原則和在瀏覽器中一樣,是先執(zhí)行微任務(wù),然后再執(zhí)行宏任務(wù),但是對于宏任務(wù)來說,是按照上圖從上到下的順序執(zhí)行的。

具體對應(yīng)的常見事件的執(zhí)行順序如下;

在微任務(wù)隊列中:

  • next tick queue:process.nextTick;
  • other queue:Promise的then回調(diào)、queueMicrotask;

(是按照從上往下的事件順序執(zhí)行)

在宏任務(wù)隊列:

  • timer queue:setTimeout、setInterval;
  • poll queue:IO事件;
  • check queue:setImmediate;
  • close queue:close事件

(同樣是按照從上往下的事件順序執(zhí)行)

所以,綜上所述,在每一次事件循環(huán)的tick中,會按照如下順序來執(zhí)行代碼:

next tick microtask queue;
other microtask queue;
timer queue;
poll queue;
check queue;
close queue

當(dāng)然,main script 依舊是最先執(zhí)行的,只有main script執(zhí)行結(jié)束后,才會按照上述順序來執(zhí)行代碼。

面試題一

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}

async function async2() {
  console.log('async2')
}

console.log('script start')

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

setTimeout(function () {
  console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

new Promise(function (resolve) {
  console.log('promise1')
  resolve();
  console.log('promise2')
}).then(function () {
  console.log('promise3')
})

console.log('script end')

// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2

第一輪事件循環(huán)里面的事件如下:

按照順序自左向右執(zhí)行,3s后執(zhí)行setTimeout2,

最后的結(jié)果是:

// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2

到此這篇關(guān)于JS事件循環(huán)-微任務(wù)-宏任務(wù)(原理講解+面試題分析)的文章就介紹到這了,更多相關(guān)循環(huán)-微任務(wù)-宏任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論