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

實例詳解JS中的事件循環(huán)機制

 更新時間:2022年04月21日 10:09:36   作者:添財青年  
JavaScript是單線程的腳本語言,所以代碼在執(zhí)行的時候,只有一個主線程來執(zhí)行所有的任務,同一個時間只能做同一件事情。本文就為大家詳細講解一下它的件循環(huán)機制,需要的可以參考一下

一、前言

之前我們把react相關鉤子函數大致介紹了一遍,這一系列完結之后我莫名感到空虛,不知道接下來應該更新有關哪方面的文章。最近想了想,打算先回歸一遍JS基礎,把一些比較重要的基礎知識點回顧一下,然后繼續(xù)擼框架(可能是源碼、也可能補全下全家桶)。不積跬步無以至千里,萬丈高樓咱們先從JS的事件循環(huán)機制開始吧,廢話不多說,開搞開搞!

在JS中,我們所有的任務可以分為同步任務和異步任務。那么什么是同步任務?什么又是異步任務呢?

同步任務:是在主線程執(zhí)行棧上排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務;比如:console.log、賦值語句等。

異步任務:不進入主線程,是進入任務隊列的任務,只有等主線程任務執(zhí)行完畢,"任務隊列"開始通知主線程,請求執(zhí)行任務,該任務才會進入主線程執(zhí)行。比如:ajax網絡請求,setTimeout 定時函數等都屬于異步任務,異步任務會通過任務隊列的機制(先進先出的機制)來進行協調。

我們執(zhí)行一段代碼時,在我們主線程的執(zhí)行棧執(zhí)行過程中,如果遇到同步任務會立即執(zhí)行,如果遇到異步任務會暫時掛起,將此異步任務推入任務隊列中(隊列的執(zhí)行機制遵循先進先出)。當主線程執(zhí)行棧里的同步任務執(zhí)行完畢后,js執(zhí)行引擎會去任務隊列中讀取掛起的異步任務并將其推入到執(zhí)行棧中執(zhí)行。這個不斷重復的過程(執(zhí)行棧執(zhí)行--->判斷同異步--->同步執(zhí)行/異步掛起推入事件對列--->??蘸笕∈录犃欣锶蝿詹⑼迫雸?zhí)行棧執(zhí)行--->繼續(xù)判斷同異步--->.......)就是本文所要介紹的事件循環(huán)。

二、宏、微任務

我們每進行一次事件循環(huán)的操作被稱之為tick,在介紹一次 tick 的執(zhí)行步驟之前,我們需要補充兩個概念:宏任務、微任務。

宏任務和微任務嚴格來說是ES6之后才有的概念(原因在于ES6提出了Promise這個概念);在Es6之后我們把JS的任務更細分成了宏任務和微任務。

其中,宏任務主要包括:script(整體代碼)、setTimeout、setInterval、I/O、UI交互事件、postMessage、requestAnimationFrame(幀動畫)、MessageChannel、setImmediate(Node.js環(huán)境);

微任務主要包括:Promise.then、MutaionObserver、process.nextTick(Node.js環(huán)境);

好了,了解了宏微任務的概念之后我們就來掰扯掰扯每次tick的執(zhí)行順序吧。首先看下圖:

三、Tick 執(zhí)行順序

1、首先執(zhí)行一個宏任務(棧中沒有就從事件隊列中獲?。?;

2、執(zhí)行過程中如果遇到微任務,就將它添加到微任務的任務隊列中、如果有宏任務的話推到相應的事件隊列中去;

3、宏任務執(zhí)行完畢后,立即執(zhí)行當前微任務隊列中的所有微任務(依次執(zhí)行);

4、當前宏任務執(zhí)行完畢,開始進行渲染;5、開始下一個宏任務(從事件隊列中獲取)開啟下一次的tick;

需要注意的是:宏任務執(zhí)行過程中如果宏任務中又添加了一個新的宏任務到任務隊列中。 這個新的宏任務會等到下一次事件循環(huán)再執(zhí)行;而微任務則不同,微任務執(zhí)行過程中如果又添加了新的微任務,則新的微任務也會在本次微任務執(zhí)行過程中被執(zhí)行,直到微任務隊列為空。每次宏任務執(zhí)行完在開啟下一次宏任務時會把微任務隊列中所有的微任務執(zhí)行完畢!

四、案例詳解

概念性的東西說完了,下面就來找些demo練練手吧!

1.摻雜setTimeout

console.log('開始');

setTimeout(()=>{
    console.log('同級的定時器');
      setTimeout(() => {
          console.log('內層的定時器');
      }, 0);
},0)

console.log('結束');

輸出結果為

開始 -> 結束 -> 同級的定時器 ->內層的定時器

解釋上述代碼:

  • 整體代碼作為一個宏任務進入主線程執(zhí)行棧中;
  • 遇到console.log('開始'),控制臺輸出 開始;
  • 遇到有一個宏任務setTimeout,JS引擎將之掛起,并推入任務隊列;
  • 遇到console.log('結束'),控制臺輸出 結束;本次宏任務執(zhí)行完畢,發(fā)現本次并無微任務,GUI進行render渲染完畢開啟下一次宏任務執(zhí)行,本次tick結束。
  • JS引擎從任務隊列拿出第一個setTimeout宏任務,將至推入主線程執(zhí)行棧, 開始進行第二個宏任務;
  • 執(zhí)行setTimeout回調,遇到 console.log('同級的定時器'),控制臺輸出 同級的定時器;
  • 遇到第二個setTimeout ,這是個本次宏任務產生的新的宏任務,將此宏任務掛起,并推入任務隊列;
  • 同樣此時發(fā)現沒有微任務,則GUI接管開始進行渲染,渲染完畢又開啟下一次宏任務,tick結束;
  • JS引擎又從任務隊列拿出第二個setTimeout宏任務,將之推入主線程執(zhí)行棧, 開始進行第三個宏任務;
  • 執(zhí)行第二個setTimeout回調,遇到 console.log('內層的定時器'),控制臺輸出 內層的定時器;
  • 本次宏任務執(zhí)行完畢發(fā)現沒有微任務,結束。

2.摻雜微任務,此處主要是Promise.then

console.log('script start');

setTimeout(function() {
  new Promise(resolve=>{
        console.log('000');
      resolve()
    }).then(res=>{
        console.log('這是微任務');
    })
  console.log('timeout1');
}, 10);

new Promise(resolve => {
    console.log('promise1');       
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})

console.log('script end');

輸出結果為:

script start -> promise1 -> script end -> then1 -> 000 -> timeout1 -> 這是微任務 -> timeout2

解釋上述代碼:

  • 整體script作為一個宏任務進入主線程執(zhí)行棧;
  • 遇到 console.log('script start') 輸出 script start ;
  • 遇到setTimeout作為新的一個宏任務連同其回調內容一同推入任務隊列 ;
  • 遇到和script start 同級的new Promise 進行執(zhí)行,此處需注意:Promise內容是同步任務,它的.then才是微任務會被推入微任務隊列。所有此處JS引擎的處理邏輯是:遇到 console.log('promise1') 輸出 promise1 ,遇到resolve() 會將 Promise的.then函數推入微任務隊列(注意,我們常說微任務時宏任務的小尾巴,指的是本次宏任務產生的微任務都會在本次宏任務執(zhí)行完之后進行執(zhí)行清空。);遇到resolve下面的setTimeout這是個新的宏任務,會被掛起并推入任務隊列。
  • 繼續(xù)順序執(zhí)行,執(zhí)行到 console.log('script end') ,輸出script end;此時第一個宏任務執(zhí)行完畢,JS引擎開始清理小尾巴(執(zhí)行并清空微任務隊列)。
  • 此時由本次執(zhí)行宏任務的過程中產生了 .then(function() { console.log('then1')}) 這個微任務,JS引擎會將此任務內的回調推入執(zhí)行棧進行執(zhí)行,輸出 then1;
  • 微任務隊列為空,開啟下一個宏任務,第一輪tick結束;
  • JS引擎從任務隊列中拿script start下面那個setTimeout宏任務將回調推入主線程執(zhí)行棧中進行執(zhí)行;
  • 遇到了Promise,執(zhí)行其內容:遇到 console.log('000') 輸出 000;
  • 執(zhí)行 resolve() 將.then函數推入微任務隊列(是此次宏任務的小尾巴);
  • 繼續(xù)執(zhí)行,遇到 console.log('timeout1') 輸出 timeout1;本次宏任務執(zhí)行完畢;
  • 宏任務執(zhí)行完畢后緊接著處理小尾巴: .then(res=>{ console.log('這是微任務'); }) 輸出 這是微任務;
  • 微任務隊列清空后,繼續(xù)開啟下一個宏任務,第二輪tick結束;
  • 將任務隊列中的 setTimeout(() => console.log('timeout2'), 10); 回調推入執(zhí)行棧中執(zhí)行,輸出 timeout2 ; 無微任務,第三輪tick結束,任務隊列也為空。

好了,相信經過這兩個例子,小伙伴們對事件循環(huán)有了初步的認識。接下來我們再頑皮一下:對上面這個demo做一丟丟微調

微調一 : 其他地方不變,then里塞定時器

setTimeout(function() {
  new Promise(resolve=>{
        console.log('000');
      resolve()
    }).then(res=>{
         setTimeout(()=>{
         console.log('這次的執(zhí)行順序呢?') -----> 如果這里再塞個定時器呢?執(zhí)行順序是什么?
        },10)
        console.log('這是微任務');
    })
  console.log('timeout1');
}, 10);

微調二:其他地方不變,對Promise進行鏈式調用

new Promise(resolve => {
    console.log('promise1');       
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
}).then(()=>{
    console.log('then2')
}).then(()=>{
    console.log('then3')
})

此Promise進行鏈式調用,其他地方不動,此時的執(zhí)行順序是什么?

提示:在一次tick結束時,此tick內微任務隊列中的微任務一定會執(zhí)行完并清空,如果在執(zhí)行過程中又產生了微任務,那么同樣會在此tick過程中執(zhí)行完畢;而宏任務的執(zhí)行則可以看成是下一次tick的開始。

3.摻雜async/await

在進行demo解析之前,我們需要補充一下async/await的相關知識點。

async

async相當于隱式返回Promise:當我們在函數前使用async的時候,使得該函數返回的是一個Promise對象,async的函數會在這里幫我們隱式使用Promise.resolve();

下面看個小demo來理解下async函數是怎么隱式轉換的:

async function test() {
    console.log('這是async函數')
    return '測試隱式轉換' 
}

上面這個async就相當于如下代碼:

function test(){
    return new Promise(function(resolve) {
      console.log('這是async函數')
       resolve('測試隱式轉換')
   })
}

await

await表示等待,是右側表達式的結果,這個表達式的計算結果可以是 Promise 對象的值或者一個函數的值(換句話說,就是沒有特殊限定)。并且await只能在帶有async的內部使用;使用await時,會從右往左執(zhí)行,當遇到await時,會阻塞函數內部處于它后面的代碼,去執(zhí)行該函數外部的 代碼 , 當外部代碼執(zhí)行完畢,再回到該函數內部執(zhí)行await后面剩余的代碼;

好了,補充完前置知識我們來做個demo助助興:

摻雜async/await的事件循環(huán)

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

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

console.log('script start');

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

async1();

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

console.log('script end');

輸出順序為

script start --> async1 start --> async2 --> promise1 --> script end --> async1 end --> promise2 --> setTimeout  

首先為方便理解我們先將async函數轉為return Promise的那種形式:

①:
async function async2() {				
    console.log('async2');     
}
轉換后如下:
function async2() {			
    return  new Promise(resolve=>{
       console.log('async2');     
    })
}


②:
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
轉換后如下:
function async1() {
  return new Promise(resolve=>{
    console.log('async1 start');
    #執(zhí)行async2,并且會阻塞其后面的代碼
    console.log('async1 end');
  })
}

所以,最后我們包含async函數的代碼塊就相當于如下代碼:

function async2() {			
    return  new Promise(resolve=>{
       console.log('async2');     
    })
}

function async1() {
  return new Promise(resolve=>{
    console.log('async1 start');
    #執(zhí)行async2,并且會阻塞其后面的代碼,在此處是阻塞了console.log('async1 end')的執(zhí)行
    console.log('async1 end');
  })
}
=============上面為聲明部分===========
console.log('script start');

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

new Promise(resolve=>{
    console.log('async1 start');
    #執(zhí)行async2,并且會阻塞其后面的代碼,在此處是阻塞了console.log(async1end)的執(zhí)行;這里相當于awaitasync2()
   
    console.log('async1 end');
  })
}

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

console.log('script end');

經過一系列騷操作之后,我們終于可以來分析這個代碼塊的執(zhí)行順序了,廢話不多說,開沖。

解釋上述代碼:

  • 首先整體代碼作為第一個宏任務進入主線程執(zhí)行棧;
  • 首先順序執(zhí)行,遇到了async2、async1 函數的聲明,不進行任何輸出;
  • 執(zhí)行到console.log('script start') 輸出 script start ;
  • 繼續(xù)執(zhí)行,遇到setTimeout宏任務,掛起并推入任務隊列;
  • 接著執(zhí)行Promise內容部分,遇到console.log('async1 start'),輸出async1 start ;
  • 這一步重點來了,遇到了await,這該怎么辦呢?別急,咱們再來看看使用await會發(fā)生什么:使用await時,會從右往左執(zhí)行,當遇到await時,會阻塞函數內部處于它后面的代碼,去執(zhí)行該函數外部的 代碼 , 當外部代碼執(zhí)行完畢,再回到該函數內部執(zhí)行await后面剩余的代碼;
  • 好了,下面開始解釋await async2():由于是是從右往左執(zhí)行,所以我們首先執(zhí)行了async2()輸出了一個Promise,我們執(zhí)行了Promise的內容輸出了async2;async2執(zhí)行完了之后,遇到await,完全不出意外,后面的代碼被阻塞;我們去執(zhí)行外面的代碼;
  • 因為console.log('async1 end')被await阻塞掉了,我們先執(zhí)行外面的代碼:執(zhí)行了外面Promise的內容,遇到了resolve(),將.then函數推入微任務隊列;然后執(zhí)行console.log('promise1'),輸出 promise1;
  • 最后執(zhí)行到console.log('script end'),輸出 script end;
  • 到此,我們外層的代碼就執(zhí)行完畢,現在想想好像少了什么?往前一看,我們console.log('async1 end')還在等待,此時,JS引擎執(zhí)行l(wèi)og輸出 async1 end 。
  • 由此,我們本次的宏任務就執(zhí)行完畢,下面看看是否有微任務,JS引擎去微任務隊列一看,好家伙,還藏著一個 then(function () {console.log('promise2');}); 把此任務回調推到執(zhí)行棧中執(zhí)行,輸出 promise2;
  • 此次tick執(zhí)行結束,開啟下一個宏任務;
  • 從任務隊列拿setTimeout這個宏任務,塞入執(zhí)行棧執(zhí)行,打印輸出setTimeout,本次無微任務,結束tick;
  • 循環(huán)結束;

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

相關文章

  • JavaScript 獲取事件對象的注意點

    JavaScript 獲取事件對象的注意點

    平時我們獲取事件對象一定要將firefox考慮進去。
    2009-07-07
  • js密碼強度校驗

    js密碼強度校驗

    這篇文章主要介紹了javascript密碼強度校驗的實現方法,并給出了詳細代碼,需要的朋友可以參考下
    2015-11-11
  • JS倒計時實例_天時分秒

    JS倒計時實例_天時分秒

    下面小編就為大家?guī)硪黄狫S倒計時實例_天時分秒。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 一篇文章帶你吃透JavaScript中的DOM知識及用法

    一篇文章帶你吃透JavaScript中的DOM知識及用法

    DOM作用:用來修改網頁內容,結構和樣式,下面這篇文章主要給大家介紹了關于如何通過一篇文章帶你吃透JavaScript中的DOM知識及用法的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-11-11
  • JS獲取當前日期時間并定時刷新示例

    JS獲取當前日期時間并定時刷新示例

    這篇文章主要介紹了JS如何獲取當前日期時間并執(zhí)行定時刷新,示例代碼如下,需要的朋友不要錯過
    2014-06-06
  • IE和Firefox的Javascript兼容性總結[推薦收藏]

    IE和Firefox的Javascript兼容性總結[推薦收藏]

    長久以來JavaScript兼容性一直是Web開發(fā)者的一個主要問題。在正式規(guī)范、事實標準以及各種實現之間的存在的差異讓許多開發(fā)者日夜煎熬
    2011-10-10
  • 關于javascript 回調函數中變量作用域的討論

    關于javascript 回調函數中變量作用域的討論

    關于回調函數中變量作用域的討論精品推薦,大家可以參考下。
    2009-09-09
  • 一文帶你掌握axios 工具函數

    一文帶你掌握axios 工具函數

    這篇文章主要為大家介紹了axios 工具函數使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • JS自動跳轉手機移動網頁的實現方法

    JS自動跳轉手機移動網頁的實現方法

    本文主要介紹了JS自動跳轉手機移動網頁的實現方法,可以通過檢查 navigator.userAgent 屬性來識別用戶代理字符串中包含的設備信息,下面就詳細的來介紹一下具體用法,感興趣的可以了解一下
    2024-03-03
  • es6中reduce的基本使用方法

    es6中reduce的基本使用方法

    這篇文章主要給大家介紹了關于es6中reduce的基本使用方法,文中通過示例代碼介紹的非常詳細,對大家學習或者使用es6具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-09-09

最新評論