從一道執行題,瞭解瀏覽器中JS執行機制

執行題

setTimeout(function(){
    console.log('定時器開始啦')
});
new Promise(function(resolve){
    console.log('立刻執行for循環啦');
    for(var i = 0; i < 10000; i++){
        if(i == 99) resolve();
    }
}).then(function(){
    console.log('執行then函數啦');
});
console.log('代碼執行結束');
// 立刻執行for循環啦
// 代碼執行結束
// 執行then函數啦
// 定時器開始啦
// 若是沒有執行對,請往下看
複製代碼

JavaScript自己是一門單線程語言

爲何 JS 是單線程的?做爲瀏覽器腳本語言,JavaScript 的主要用途是與用戶互動,以及操做 DOM 。這決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定 JavaScript 同時有兩個線程,一個線程在某個 DOM 節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?javascript

同步執行和異步執行

單線程就意味着,全部任務都須要排隊,前一個任務結束,才能執行後一個任務。若是前一個任務耗時很長,那麼後一個任務就不得不一直等待 因而乎,JS 設計者們把全部任務分紅兩類,同步和異步前端

  1. 同步:只有前一個任務執行完畢,才能執行後一個任務
  2. 異步:當同步任務執行到某個 WebAPI 時,就會觸發異步操做,此時瀏覽器會單獨開線程去處理這些異步任務。

任務隊列、回調隊列、事件循環

WebAPI 是啥?瀏覽器事件、定時器、ajax,這些操做不會阻塞 JS 的執行,JS 會跳過當前代碼,執行後續代碼java

  1. 任務隊列( Task Queue ):主線程執行完畢後所觸發的異步任務( WebAPIs ),叫任務隊列
  2. 回調隊列( Callback Queue ):這些異步 WebAPI 執行完成後獲得的結果,會添加到 callback queue
  3. 事件循環( Event Loop ):只要主線程的同步任務執行完畢,就會不斷的讀取 "回調隊列" 中的回調函數,到主線程中執行,這個過程不斷循環往復

如何知道主線程執行執行完畢?JS引擎存在 monitoring process 進程,會持續不斷的檢查主線程執行爲空,一旦爲空,就會去 callback queue 中檢查是否有等待被調用的函數。node

說了一堆概念,來一塊兒看看這段代碼

console.log('1');
setTimeout(function() {
    console.log('2');
}, 0);
console.log('3');
複製代碼

執行結果以下:ajax

  1. 打印1
  2. 遇到 WebAPI( setTimeout ) ,瀏覽器新開定時器線程處理,執行完成後把回調函數存放到回調隊列中。專業一點的說發: JS 引擎遇到異步任務後不會一直等待其返回結果,而是將這個任務掛起交給其餘瀏覽器線程處理,本身繼續執行主線程中的其餘任務。這個異步任務執行完畢後,把結果返回給回調隊列。被放入的代碼不會被當即執行。而是當主線程全部同步任務執行完畢, monitoring process 進程就會把 "回調隊列" 中的第一個回調代碼放入主線程。而後主線程執行代碼。如此反覆
  3. 打印3 異步 setTimeout 不會阻塞同步代碼,所以會首先打印3
  4. 主線程執行完畢後,執行 Callback Queue 打印2

macro task 與 micro task

異步任務的執行優先級並不相同,它們被分爲兩類:微任務( micro task ) 和 宏任務( macro task ) 根據異步事件的類型,這些事件實際上會被派發對應的宏任務和微任務中,在當前主線程執行完畢後,promise

  1. 會先查看微任務中是否有事件存在,若是不存在,則再去找宏任務
  2. 若是存在,則會依次執行隊列中的參數,直到微任務列表爲空,讓後去宏任務中一次讀取事件到主線程中執行,如此反覆 當前主線程執行完畢後,會首先處理微任務隊列中的事件,讓後再去讀取宏任務隊列的事件。在同一次事件循環中,微任務永遠在宏任務以前執行。
  1. 宏任務( macro-task ):總體 scriptsetTimeoutsetIntervalUI交互事件I/O
  2. 微任務( micro-task ):process.nextTickPromiseMutaionObserver

總體script自己就是一次宏任務瀏覽器

上代碼

(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function (resolve, reject) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()
1. setTimeout:宏任務:存入宏任務隊列
2. Promise:函數自己是同步執行的( **Promise** 只有一個參數,默認new的時候就會同步執行), `.then` 是異步,所以依次打印12  `.then` 存入微任務中
3. 打印3( 第一次主線程執行完畢 )
4. 執行微任務中的回調函數:5, 讓後執行宏任務中的 `setTimeout` 4
// 最終結果1,2,3,5,4
複製代碼

來點稍微高難度的

console.log(1)

setTimeout(() => {
    console.log(2)
    new Promise(resolve => {
        console.log(4)
        resolve()
    }).then(() => {
        console.log(5)
    })
})

new Promise(resolve => {
    console.log(7)
    resolve()
}).then(() => {
    console.log(8)
})

setTimeout(() => {
    console.log(9)
    new Promise(resolve => {
        console.log(11)
        resolve()
    }).then(() => {
        console.log(12)
    })
})
第一次同步執行:1,7,8
    宏任務:setTimeout
2,4,5
9,11,12
複製代碼

應用場景

console.log('先執行這裏');
setTimeout(() => {
    console.log('再執行啦');
}, 0);
複製代碼
  1. 在工做中,常常會遇到上述的代碼,含義:只要主線程執行完成,就立馬執行 setTimeout 中的回調代碼
  2. micro-task 優先於 macro-task 執行,在瀏覽器中高優先級的代碼能夠在 promisesetTimeout(()=>{}, 0) 中執行
  3. 認知尚淺,只能想到這些應用場景

總結

這些概念是中級以上前端開發必知必會內容,其實實際的落地場景不多異步

  1. 爲何 new Promise 第一個參數是同步執行的 ?學習Promise && 簡易實現Promise
  2. node 中的 JS 執行機制是什麼樣子的?從一道執行題,瞭解Node中JS執行機制

附:這篇博客 也許 想表達 瀏覽器環境中JS同步和異步的執行過程 (⊙﹏⊙)b函數

相關文章
相關標籤/搜索