亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

前言:

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

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

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

引用:

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

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

圖解:

在這里插入圖片描述

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

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

其中常見的異步操作有:ajax請求,setTimeout,還有類似onclik事件等

三、任務(wù)隊列:

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

首先,顧名思義,既然是一個隊列,那么就遵循FIFO原則

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

同一任務(wù)隊列內(nèi),按隊列順序被主線程取走;
不同任務(wù)隊列之間,存在著優(yōu)先級,優(yōu)先級高的優(yōu)先獲?。ㄈ缬脩鬒/O)

3.1 任務(wù)隊列的類型:

任務(wù)隊列分為 宏任務(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) 唯一,整個事件循環(huán)當中,僅存在一個;
(2) 執(zhí)行為同步,同一個事件循環(huán)中的microtask會按隊列順序,串行執(zhí)行完畢;

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

宏任務(wù)macrotask queue:

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

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

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

先用一個簡單的例子加深一下理解:

console.log('1, time = ' + new Date().toString()) // 1.進入主線程,執(zhí)行同步任務(wù),輸出1
setTimeout(macroCallback, 0)// 2. 加入宏任務(wù)隊列 // 7.開始執(zhí)行此定時器宏任務(wù),調(diào)用macroCallback,輸出4
new Promise(function (resolve, reject) {//3.加入微任務(wù)隊列
  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())
}

運行結(jié)果:

請?zhí)砑訄D片描述

四、強大的異步專家 process.nextTick()

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

請?zhí)砑訄D片描述

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

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

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

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

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

Promise.resolve().then(() => {
  // 2. 將 then 任務(wù)分發(fā)到本輪循環(huán)微任務(wù)隊列中去
  console.log('then1') // 8. 執(zhí)行 then 微任務(wù), 打印 then1,flag 此時是 true 了
  flag = true
})
new Promise(resolve => {
  // 3. 執(zhí)行 Promise 里 同步代碼
  console.log('promise')
  resolve()
  setTimeout(() => { // 4. 將定時器里的任務(wù)放到宏任務(wù)隊列中
    console.log('timeout2') // 11. 執(zhí)行定時器宏任務(wù) 這邊指定了 10 的等待時長, 因此在另一個定時器任務(wù)之后執(zhí)行了
  }, 10)
}).then(function () {
  // 5. 將 then 任務(wù)分發(fā)到本輪循環(huán)微任務(wù)隊列中去
  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ù)隊列中,等本輪`tick`執(zhí)行完,下一次事件循環(huán)再執(zhí)行
}
f1(() => console.log('f為:', flag ? '異步' : '同步')) // 6. 打印 `f為:同步`
f2(() => {
  console.log('timeout1,', 'f為:', flag ? '異步' : '同步') // 10. 執(zhí)行定時器宏任務(wù)
})

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

運行結(jié)果:

請?zhí)砑訄D片描述

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

官方的例子:

let bar;

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

// 回調(diào)函數(shù)在`someAsyncApiCall`完成之前被調(diào)用
someAsyncApiCall(() => {
  // 由于`someAsyncApiCall`已經(jīng)完成,bar沒有被分配任何值
  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;

再看一個含有 process.nextTick的例子:

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

setTimeout(function () { //2.它的回調(diào)函數(shù)被加入 宏任務(wù)隊列中
	//7.目前微任務(wù)隊列為空,所以取出 宏任務(wù)隊列首項,執(zhí)行此任務(wù)
    console.log('2'); // 輸出2
    process.nextTick(function () { // 16.上一次循環(huán)結(jié)束,在下一次宏任務(wù)開始之前調(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.檢測到異步方法then,將其回調(diào)函數(shù)加入 微任務(wù)隊列中
        console.log('5'); // 10. 取出微任務(wù)隊列首項,也就是這個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.檢測到異步方法then,將其回調(diào)函數(shù)加入 微任務(wù)隊列中
    console.log('8'); //6. 主線程執(zhí)行完畢,取出微任務(wù)隊列中首項,將其回調(diào)函數(shù)壓入執(zhí)行棧,輸出8
})

setTimeout(function () { //5.它的回調(diào)函數(shù) 加入 宏任務(wù)隊列中
	//12.此刻,微任務(wù)隊列為空,開始執(zhí)行此宏任務(wù)
    console.log('9'); // 輸出9
    process.nextTick(function () { // 17.此刻 微任務(wù)和宏任務(wù)隊列都為空了,此次循環(huán)自動結(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.檢測到then異步方法,加入微任務(wù)隊列
        console.log('12');//15.取出微任務(wù)隊列首項,執(zhí)行此then微任務(wù),輸出12
    })

})

運行結(jié)果:

請?zhí)砑訄D片描述

此過程步驟詳解:

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

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

代碼運行結(jié)果:

請?zhí)砑訄D片描述

請?zhí)砑訄D片描述

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

相關(guān)文章

最新評論