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

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

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

前言

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

由此,可以得到執(zhí)行順序?yàn)椋簃ain script > 微任務(wù)隊(duì)列里面的事件 > 宏任務(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

分析如下:

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

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

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

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

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

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

之后執(zhí)行setTimeout1的宏任務(wù),此時(shí)第二輪事件循環(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ī)范來實(shí)現(xiàn)的,不同的瀏覽器可能會(huì)有不同的實(shí)現(xiàn),而Node中是由libuv實(shí)現(xiàn)的。

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

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

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

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

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

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

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

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

  1. 定時(shí)器(Timers):本階段執(zhí)行已經(jīng)被 setTimeout() 和 setInterval() 的調(diào)度回調(diào)函數(shù)。
  2. 待定回調(diào)(Pending Callback):對(duì)某些系統(tǒng)操作(如TCP錯(cuò)誤類型)執(zhí)行回調(diào),比如TCP連接時(shí)接收到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ù),但是對(duì)于宏任務(wù)來說,是按照上圖從上到下的順序執(zhí)行的。

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

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

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

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

在宏任務(wù)隊(duì)列:

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

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

所以,綜上所述,在每一次事件循環(huán)的tick中,會(huì)按照如下順序來執(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é)束后,才會(huì)按照上述順序來執(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論