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

JavaScript 關(guān)于事件循環(huán)機(jī)制的刨析

 更新時(shí)間:2021年11月17日 15:32:26   作者:Ocean??!  
js里的事件循環(huán)機(jī)制十分有趣。從很多面試題也可以看出來(lái),考察簡(jiǎn)單的setTimeout也就是考察這個(gè)機(jī)制的,接下來(lái)本文帶你詳細(xì)了解它

前言:

這次主要整理一下自己對(duì) Js事件循環(huán)機(jī)制,同步,異步任務(wù),宏任務(wù),微任務(wù)的理解,大概率暫時(shí)還有些偏差或者錯(cuò)誤。如果有,十分歡迎各位糾正我的錯(cuò)誤!

一、事件循環(huán)和任務(wù)隊(duì)列產(chǎn)生的原因:

首先,JS是單線程,這樣設(shè)計(jì)也是具有合理性的,試想如果一邊進(jìn)行dom的刪除,另一邊又進(jìn)行dom的添加,瀏覽器該如何處理?

引用:

單線程即任務(wù)是串行的,后一個(gè)任務(wù)需要等待前一個(gè)任務(wù)的執(zhí)行,這就可能出現(xiàn)長(zhǎng)時(shí)間的等待。但由于類(lèi)似ajax網(wǎng)絡(luò)請(qǐng)求、setTimeout時(shí)間延遲、DOM事件的用戶(hù)交互等,這些任務(wù)并不消耗 CPU,是一種空等,資源浪費(fèi),因此出現(xiàn)了異步。通過(guò)將任務(wù)交給相應(yīng)的異步模塊去處理,主線程的效率大大提升,可以并行的去處理其他的操作。當(dāng)異步處理完成,主線程空閑時(shí),主線程讀取相應(yīng)的callback,進(jìn)行后續(xù)的操作,最大程度的利用CPU。此時(shí)出現(xiàn)了同步執(zhí)行和異步執(zhí)行的概念,同步執(zhí)行是主線程按照順序,串行執(zhí)行任務(wù);異步執(zhí)行就是cpu跳過(guò)等待,先處理后續(xù)的任務(wù)(CPU與網(wǎng)絡(luò)模塊、timer等并行進(jìn)行任務(wù))。由此產(chǎn)生了任務(wù)隊(duì)列與事件循環(huán),來(lái)協(xié)調(diào)主線程與異步模塊之間的工作。“”

二、事件循環(huán)機(jī)制:

圖解:

在這里插入圖片描述

首先把JS執(zhí)行代碼操作 分為主線程,任務(wù)隊(duì)列,任何一段js代碼的執(zhí)行都可以分為以下幾個(gè)步驟:

步驟一: 主線程讀取JS代碼,此時(shí)為同步環(huán)境,形成相應(yīng)的堆和執(zhí)行棧;
步驟二: 當(dāng)主線程遇到異步操作的時(shí)候,將異步操作交給對(duì)應(yīng)的API進(jìn)行處理;
步驟三: 當(dāng)異步操作處理完成,推入任務(wù)隊(duì)列中
步驟四: 主線程執(zhí)行完畢后,查詢(xún)?nèi)蝿?wù)隊(duì)列,取出一個(gè)任務(wù),并推入主線程進(jìn)行處理
步驟五: 重復(fù)步驟二、三、四

其中常見(jiàn)的異步操作有:ajax請(qǐng)求,setTimeout,還有類(lèi)似onclik事件等

三、任務(wù)隊(duì)列:

同步和異步任務(wù)分別進(jìn)入不同的執(zhí)行環(huán)境,同步的進(jìn)入主線程,即主執(zhí)行棧,異步的進(jìn)入任務(wù)隊(duì)列

首先,顧名思義,既然是一個(gè)隊(duì)列,那么就遵循FIFO原則

如上示意圖,任務(wù)隊(duì)列存在多個(gè),它們的執(zhí)行順序:

同一任務(wù)隊(duì)列內(nèi),按隊(duì)列順序被主線程取走;
不同任務(wù)隊(duì)列之間,存在著優(yōu)先級(jí),優(yōu)先級(jí)高的優(yōu)先獲取(如用戶(hù)I/O)

3.1 任務(wù)隊(duì)列的類(lèi)型:

任務(wù)隊(duì)列分為 宏任務(wù)(macrotask queue)微任務(wù)(microtask queue)

宏任務(wù)主要包含:script( 整體代碼)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 環(huán)境)

微任務(wù)主要包含:Promise、MutaionObserver、process.nextTick(Node.js 環(huán)境)

3.2 兩者區(qū)別:

微任務(wù)microtask queue:

(1) 唯一,整個(gè)事件循環(huán)當(dāng)中,僅存在一個(gè);
(2) 執(zhí)行為同步,同一個(gè)事件循環(huán)中的microtask會(huì)按隊(duì)列順序,串行執(zhí)行完畢;

PS:所以利用microtask queue可以形成一個(gè)同步執(zhí)行的環(huán)境

宏任務(wù)macrotask queue:

(1) 不唯一,存在一定的優(yōu)先級(jí)(用戶(hù)I/O部分優(yōu)先級(jí)更高)
(2) 異步執(zhí)行,同一事件循環(huán)中,只執(zhí)行一個(gè)

3.3 更細(xì)致的事件循環(huán)過(guò)程

  •  一、二、三、步同上
  • 主線程查詢(xún)?nèi)蝿?wù)隊(duì)列,執(zhí)行microtask queue,將其按序執(zhí)行,全部執(zhí)行完畢;
  • 主線程查詢(xún)?nèi)蝿?wù)隊(duì)列,執(zhí)行macrotask queue,取隊(duì)首任務(wù)執(zhí)行,執(zhí)行完畢;
  • 重復(fù)四、五步驟;

先用一個(gè)簡(jiǎn)單的例子加深一下理解:

console.log('1, time = ' + new Date().toString()) // 1.進(jìn)入主線程,執(zhí)行同步任務(wù),輸出1
setTimeout(macroCallback, 0)// 2. 加入宏任務(wù)隊(duì)列 // 7.開(kāi)始執(zhí)行此定時(shí)器宏任務(wù),調(diào)用macroCallback,輸出4
new Promise(function (resolve, reject) {//3.加入微任務(wù)隊(duì)列
  console.log('2, time = ' + new Date().toString())//4.執(zhí)行此微任務(wù)中的同步代碼,輸出2
  resolve()
  console.log('3, time = ' + new Date().toString())//5.輸出3
}).then(microCallback)// 6.執(zhí)行then微任務(wù),調(diào)用microCallback,輸出5

//函數(shù)定義
function macroCallback() {
  console.log('4, time = ' + new Date().toString())
}

function microCallback() {
  console.log('5, time = ' + new Date().toString())
}

運(yùn)行結(jié)果:

請(qǐng)?zhí)砑訄D片描述

四、強(qiáng)大的異步專(zhuān)家 process.nextTick()

第一次看見(jiàn)這東西,有點(diǎn)眼熟啊,想了一下好像之前vue項(xiàng)目中 用過(guò) this.$nextTick(callback) 當(dāng)時(shí)說(shuō)的是 當(dāng)頁(yè)面上元素被重新渲染之后 才會(huì)執(zhí)行回調(diào)函數(shù)中的代碼
,不是很理解,暫時(shí)記住吧

請(qǐng)?zhí)砑訄D片描述

4.1 process.nextTick()在何時(shí)調(diào)用?

任何時(shí)候在給定的階段中調(diào)用 process.nextTick(),所有傳遞到 process.nextTick() 的回調(diào)將在事件循環(huán)繼續(xù)之前解析

在事件循環(huán)中,每進(jìn)行一次循環(huán)操作稱(chēng)為tick,知道了這個(gè)之后,對(duì)理解這個(gè)方法什么時(shí)候調(diào)用瞬間明白了一些!

再借用別人的例子,加深一下對(duì)事件循環(huán)的理解吧:

var flag = false // 1. 變量聲明

Promise.resolve().then(() => {
  // 2. 將 then 任務(wù)分發(fā)到本輪循環(huán)微任務(wù)隊(duì)列中去
  console.log('then1') // 8. 執(zhí)行 then 微任務(wù), 打印 then1,flag 此時(shí)是 true 了
  flag = true
})
new Promise(resolve => {
  // 3. 執(zhí)行 Promise 里 同步代碼
  console.log('promise')
  resolve()
  setTimeout(() => { // 4. 將定時(shí)器里的任務(wù)放到宏任務(wù)隊(duì)列中
    console.log('timeout2') // 11. 執(zhí)行定時(shí)器宏任務(wù) 這邊指定了 10 的等待時(shí)長(zhǎng), 因此在另一個(gè)定時(shí)器任務(wù)之后執(zhí)行了
  }, 10)
}).then(function () {
  // 5. 將 then 任務(wù)分發(fā)到本輪循環(huán)微任務(wù)隊(duì)列中去
  console.log('then2') // 9. 執(zhí)行 then 微任務(wù), 打印 then2,至此本輪 tick 結(jié)束
})
function f1(f) {
  // 1. 函數(shù)聲明
  f()
}
function f2(f) {
  // 1. 函數(shù)聲明
  setTimeout(f) //  7. 把`setTimeout`中的`f`放到宏任務(wù)隊(duì)列中,等本輪`tick`執(zhí)行完,下一次事件循環(huán)再執(zhí)行
}
f1(() => console.log('f為:', flag ? '異步' : '同步')) // 6. 打印 `f為:同步`
f2(() => {
  console.log('timeout1,', 'f為:', flag ? '異步' : '同步') // 10. 執(zhí)行定時(shí)器宏任務(wù)
})

console.log('本輪宏任務(wù)執(zhí)行完') // 7. 打印

運(yùn)行結(jié)果:

請(qǐng)?zhí)砑訄D片描述

process.nextTick 中的回調(diào)是在當(dāng)前tick執(zhí)行完之后,下一個(gè)宏任務(wù)執(zhí)行之前調(diào)用的。

官方的例子:

let bar;

// 這個(gè)方法用的是一個(gè)異步簽名,但其實(shí)它是同步方式調(diào)用回調(diào)的
function someAsyncApiCall(callback) { callback(); }

// 回調(diào)函數(shù)在`someAsyncApiCall`完成之前被調(diào)用
someAsyncApiCall(() => {
  // 由于`someAsyncApiCall`已經(jīng)完成,bar沒(méi)有被分配任何值
  console.log('bar', bar); // undefined
});

bar = 1;

使用 process.nextTick:

let bar;

function someAsyncApiCall(callback) {
  process.nextTick(callback);
}

someAsyncApiCall(() => {
  console.log('bar', bar); // 1
});

bar = 1;

再看一個(gè)含有 process.nextTick的例子:

console.log('1'); // 1.壓入主線程執(zhí)行棧,輸出1

setTimeout(function () { //2.它的回調(diào)函數(shù)被加入 宏任務(wù)隊(duì)列中
	//7.目前微任務(wù)隊(duì)列為空,所以取出 宏任務(wù)隊(duì)列首項(xiàng),執(zhí)行此任務(wù)
    console.log('2'); // 輸出2
    process.nextTick(function () { // 16.上一次循環(huán)結(jié)束,在下一次宏任務(wù)開(kāi)始之前調(diào)用,輸出3
        console.log('3'); 
    })
    new Promise(function (resolve) {
    	//8.執(zhí)行 此promise的同步任務(wù),輸出4,狀態(tài)變?yōu)閞esolve
        console.log('4');
        resolve();
    }).then(function () {//9.檢測(cè)到異步方法then,將其回調(diào)函數(shù)加入 微任務(wù)隊(duì)列中
        console.log('5'); // 10. 取出微任務(wù)隊(duì)列首項(xiàng),也就是這個(gè)then的回調(diào),執(zhí)行,輸出5
    })
})

process.nextTick(function () { // 11.一次事件循環(huán)結(jié)束,執(zhí)行nextTick()的回調(diào),輸出6
    console.log('6');
})
new Promise(function (resolve) { 
	//3.執(zhí)行promise中的同步任務(wù) 輸出7,狀態(tài)變?yōu)閞esolve
    console.log('7');
    resolve();
}).then(function () { //4.檢測(cè)到異步方法then,將其回調(diào)函數(shù)加入 微任務(wù)隊(duì)列中
    console.log('8'); //6. 主線程執(zhí)行完畢,取出微任務(wù)隊(duì)列中首項(xiàng),將其回調(diào)函數(shù)壓入執(zhí)行棧,輸出8
})

setTimeout(function () { //5.它的回調(diào)函數(shù) 加入 宏任務(wù)隊(duì)列中
	//12.此刻,微任務(wù)隊(duì)列為空,開(kāi)始執(zhí)行此宏任務(wù)
    console.log('9'); // 輸出9
    process.nextTick(function () { // 17.此刻 微任務(wù)和宏任務(wù)隊(duì)列都為空了,此次循環(huán)自動(dòng)結(jié)束,執(zhí)行此回調(diào),輸出10
        console.log('10');
    })
    new Promise(function (resolve) {
    	//13. 執(zhí)行此promise的同步任務(wù),輸出11,狀態(tài)改變
        console.log('11');
        resolve();
    }).then(function () {//14.檢測(cè)到then異步方法,加入微任務(wù)隊(duì)列
        console.log('12');//15.取出微任務(wù)隊(duì)列首項(xiàng),執(zhí)行此then微任務(wù),輸出12
    })

})

運(yùn)行結(jié)果:

請(qǐng)?zhí)砑訄D片描述

此過(guò)程步驟詳解:

  • 首先進(jìn)入主線程,檢測(cè)到log只是普通函數(shù),壓入執(zhí)行棧,輸出1;
  • 檢測(cè)到setTimeout為特殊的異步方法(macrotask),將其交由其他內(nèi)核模塊處理,setTimeout的回調(diào)函數(shù)被放入宏任務(wù)(macrotask)隊(duì)列中;
  • 檢測(cè)到promise對(duì)象以及其中的resolve是一般的方法,將其同步任務(wù)壓入執(zhí)行棧,輸出7,并且狀態(tài)改變?yōu)閞essolve;
  • 檢測(cè)到剛才的promise對(duì)象的then方法是異步方法,將其交由其他內(nèi)核模塊處理,回調(diào)函數(shù)被放入微任務(wù)(microtask)隊(duì)列中;
  • 又檢測(cè)到一個(gè)setTimeout為特殊的異步方法,其回調(diào)函數(shù)被放入宏任務(wù)(macrotask)隊(duì)列中;
  • 此時(shí),主線程空了,開(kāi)始從任務(wù)隊(duì)列中取,取出 微任務(wù)隊(duì)列首項(xiàng),也就是第一個(gè)promise的then方法的回調(diào),執(zhí)行,輸出8;
  • 檢查此時(shí)微任務(wù)隊(duì)列為空,取出宏任務(wù)隊(duì)列首項(xiàng),也就是第一個(gè)setTimeOut,執(zhí)行其回調(diào)函數(shù),輸出2;
  • 在它的回調(diào)中碰到一個(gè)promise,執(zhí)行其同步任務(wù),輸出4,狀態(tài)改變;
  • 然后檢測(cè)到then,同上,加入到微任務(wù)隊(duì)列;
  • 取出微任務(wù)隊(duì)列首項(xiàng)到主線程執(zhí)行,也就是剛才的then,輸出5;
  • 此次循環(huán)結(jié)束,在下一個(gè)宏任務(wù)開(kāi)始之前,調(diào)用第一個(gè)process.nextTick()的回調(diào),輸出6;
  • 開(kāi)始下一個(gè)宏任務(wù),取出宏任務(wù)隊(duì)列首項(xiàng),也就是第二個(gè)setTimeout的回調(diào),將其壓入執(zhí)行棧,輸出9;
  • 然后將里面的promise對(duì)象的同步任務(wù)壓入執(zhí)行棧,輸出11,狀態(tài)改為resolve;
  • 這時(shí)又檢測(cè)到異步then方法,同上,將其回調(diào)加入 微任務(wù)隊(duì)列;
  • 取出微任務(wù)隊(duì)列首項(xiàng),也就是剛才的then回調(diào),輸出12;
  • 此次循環(huán)結(jié)束,在下一次宏任務(wù)開(kāi)始之前執(zhí)行,process.nextTick()的回調(diào),輸出3;
  • 此時(shí)發(fā)現(xiàn) 任務(wù)隊(duì)列和主線程都空了,此次事件循環(huán)自動(dòng)結(jié)束,執(zhí)行最后一個(gè)process.nextTick()的回調(diào),輸出10;

結(jié)束!趁著靈光乍現(xiàn)的時(shí)候,噼里啪啦趕緊記錄下來(lái),后面再檢查檢查是否有問(wèn)題,也歡迎各位指出我的錯(cuò)誤。

再來(lái)分析一個(gè)簡(jiǎn)單的例子:

console.log('0');
setTimeout(() => {
    console.log('1');
    new Promise(function(resolve) {
        console.log('2');
        resolve();
    }).then(()=>{
        console.log('3');
    })
    new Promise(resolve => {
        console.log('4');
        for(let i=0;i<9;i++){
            i == 7 && resolve();
        }
        console.log('5');
    }).then(() => {
        console.log('6');
    })
})
  • 進(jìn)入主線程,檢測(cè)到log為普通函數(shù),壓入執(zhí)行棧,輸出0;
  • 檢測(cè)到setTimeOut是特殊的異步方法,交給其他模塊處理,其回調(diào)函數(shù)加入 宏任務(wù)(macrotask)隊(duì)列;
  • 此時(shí)主線程中已經(jīng)沒(méi)有任務(wù),開(kāi)始從任務(wù)隊(duì)列中取;
  • 發(fā)現(xiàn)為任務(wù)隊(duì)列為空,則取出宏任務(wù)隊(duì)列首項(xiàng),也就是剛才的定時(shí)器的回調(diào)函數(shù);
  • 執(zhí)行其中的同步任務(wù),輸出1;
  • 檢測(cè)到promise及其resolve方法是一般的方法,壓入執(zhí)行棧,輸出2,狀態(tài)改變?yōu)閞esolve;
  • 檢測(cè)到這個(gè)promise的then方法是異步方法,將其回調(diào)函數(shù)加入 微任務(wù)隊(duì)列;
  • 緊接著又檢測(cè)到一個(gè)promise,執(zhí)行其中的同步任務(wù),輸出4,5,狀態(tài)改變?yōu)閞esolve;
  • 然后將它的then異步方法加入微任務(wù)隊(duì)列;
  • 執(zhí)行微任務(wù)隊(duì)列首項(xiàng),也就是第一個(gè)promise的then,輸出3;
  • 再取出為任務(wù)隊(duì)列首項(xiàng),也就是第二個(gè)promise的then,輸出6;
  • 此時(shí)主線程和任務(wù)隊(duì)列都為空,執(zhí)行完畢;

代碼運(yùn)行結(jié)果:

請(qǐng)?zhí)砑訄D片描述

請(qǐng)?zhí)砑訄D片描述

到此這篇關(guān)于JavaScript 關(guān)于事件循環(huán)機(jī)制的刨析的文章就介紹到這了,更多相關(guān)JavaScript 事件循環(huán)機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論