深刻理解Javascript之Callstack&EventLoop

1.概述

衆所周知,Javascript是一個單線程的語言。這意味着,在Javascript中,同一時間只能作一件事情。javascript

這樣的設計有一些優勢,例如簡單,避免了多線程中複雜的狀態同步,寫程序時不用考慮併發訪問。但同時也帶來了一些其餘問題,其中比較突出的一個問題是:代碼邏輯不直觀。因爲Javascript是單線程的,其中只有一個執行序列。因此,在執行異步操做(例如定時,網絡請求這些不能當即完成的操做)時,Javascript運行時不可能在那裏等着操做完成。不然整個運行時都被阻塞在那裏了,致使其餘全部的操做都沒法進行,例如網頁渲染,用戶點擊、滾動頁面等操做。這樣的用戶體驗是很是糟糕的。java

正由於如此,Javascript使用回調函數處理異步操做結果。進行異步操做時傳入一個回調,操做完成以後由Javascript引擎執行這個回調,將結果傳入。慢慢地,Javascript中充斥着大量的回調。過多的使用回調讓一段完整的邏輯被拆分紅了不少片斷,很是不利於閱讀與維護。回調過多的問題在NodeJS中更爲突出,故而出現了Promise(見個人前一篇博客)和async/awaitgit

那麼異步操做完成時,Javascript運行時是怎樣感知到並調用對應的回調函數的呢?github

答案是EventLoop(事件循環)。編程

要了解EventLoop是怎樣運做的,咱們首先須要瞭解Javascript是怎樣處理一個個任務,調用一個個函數的。這就是CallStack(調用棧)所作的事。promise

2.調用棧

相信有過其餘語言編程經驗的讀者都據說過CallStack的概念。Javascript中的CallStack相似。瀏覽器

CallStack是一個棧結構,棧的特色是LIFO(後入先出),出棧入棧只會在一端(也就是棧頂)進行。bash

CallStack是用來處理函數調用與返回的。每次調用一個函數,Javascript運行時會生成一個新的調用結構壓入CallStack。而函數調用結束返回時,JavaScript運行時會將棧頂的調用結構彈出。因爲棧的LIFO特性,每次彈出的必然是最新調用的那個函數的結構。網絡

Javascript啓動時,從文件或標準輸入加載程序。加載完成時,Javascript運行時會生成一個匿名的函數,函數體就是輸入的代碼。這個函數就有點相似於C/C++中的main()函數,是咱們的入口函數。咱們姑且稱之爲<main>函數。Javascript啓動時,首先調用就是<main>函數。看下面代碼:多線程

function func1() {
    console.log('in function1');
}

function func2() {
    func1();
    console.log('in function2');
}

function func3() {
    func2();
    console.log('in function3');
}

func3();
複製代碼

上面代碼很好理解,咱們來看看Javascript是如何運行這段代碼的。

Javascript首先加載代碼,建立一個匿名<main>包裹這段代碼並調用該函數。<main>函數執行,依次定義函數func1func2func3,而後調用函數func3。爲func3建立調用結構並壓棧。函數func3中調用func2,爲func2建立調用結構並壓棧。函數func2中調用func1,爲func1建立調用結構並壓棧。這個過程當中,CallStack的變化以下。

Call Stack Push

而後,函數func1執行完成,從棧頂彈出調用結構。而後func2繼續執行,func2執行完成後從棧頂彈出其調用結構。而後func3繼續執行,func3執行完成後從棧頂彈出其調用結構。這個過程當中,CallStack的變化以下。

Call Stack Pop

固然,我這裏有一個地方不太嚴謹。不知道讀者有沒有注意到,console.log也是函數函數哦。因此在func1中調用console.log時,CallStack上也會有對應的調用堆棧。func2func3中的console.log調用一樣如此。有興趣的話,能夠本身畫一畫完整的調用流程,這樣能夠加深理解😀。

這裏我推薦你們使用Google Chrome的開發者工具來幫助咱們理解CallStack。下圖是上面代碼在開發者工具中的一步步執行的結果:

Call Stack Demo

  • Chrome中<main>稱爲<anonymous>

  • 單步執行時,重點觀察右側工具欄中Call Stack一欄的變化。

在CallStack中執行的函數,咱們稱之爲一個task(任務)。

接下來,咱們來思考這樣一個問題:setTimeoutsetIntervalAJAX請求這些功能是怎麼實現的?

一位牛人Philip Roberts曾經將V8引擎(Chrome內置的Javascript引擎)源碼下載下來,而後用grep查找,發現源碼中並無實現這些函數的代碼😂。

那麼這些函數究竟是如何實現的,又是如何與Javascript引擎交互的呢?

答案是:宿主(網頁中指的是瀏覽器,Node中指的是Node引擎)提供實現,並在操做完成時將結果(異步的,會有延遲)放入Javascript引擎的task隊列,由Javascript引擎處理。

3.事件循環

EventLoop顧名思義,其實就是Javascript引擎中的一個循環,它就是一個不停地從任務隊列(task queue)中取出任務執行的過程。

咱們前面詳細瞭解了CallStack以及Javascript啓動時是如何處理的。可是<main>退出後,Javascript引擎就沒事作了嗎?固然不是,有不少任務會不定時的觸發須要Javascript引擎去處理。例如,用戶點擊按鈕,定時器,頁面渲染等。

其實,Javascript引擎中維護着一個任務隊列。當CallStack中沒有任務在執行時,引擎會從任務隊列中取出任務壓入CallStack處理。咱們經過代碼來具體看看(引用jesstelford):

setTimeout(() => {
    console.log('hi');
}, 1000);
複製代碼

咱們的Js代碼,call stack,task queue和Web APIs(瀏覽器中實現)關係以下:

[code]        |   [call stack]    | [task queue] | |    [Web APIs] |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
複製代碼

開始時,代碼未執行,全部都是空的。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <main>            |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
複製代碼

開始執行代碼,壓入咱們的<main>函數。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <main>            |              | |               |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
複製代碼

執行第一行代碼,調用函數setTimeout。咱們前面說過,每一個函數調用都會建立一個新的調用記錄壓到棧上。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <main>            |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
複製代碼

setTimeout執行完成,從棧中移除對應調用記錄。Web APIs記錄超時和回調,超時機制由瀏覽器實現。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
複製代碼

代碼中沒有其餘邏輯,<main>函數結束,從棧移除調用信息。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   | function   <-----timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
複製代碼

超時時間到了,Web APIs將回調放入task queue中。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function        <---function     | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
複製代碼

EventLoop檢測到Javascript沒有任務處理,從task queue中取出任務執行。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
>   console.log('hi') | console.log       |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
複製代碼

執行該回調函數,回調函數中又調用了console.log函數。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi
複製代碼

console.log執行完成,輸出"hi"。

[code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi
複製代碼

回調函數執行完成,CallStack再次爲空。

上面就是setTimeout的執行過程,從中開始看出EventLoop在幕後作的工做。

下面咱們再來看一段代碼:

console.log("start");

setTimeout(() => {
    console.log("timeout");
}, 0);

Promise.resolve()
  .then(() => {
      console.log("promise1");
  })
  .then(() => {
      console.log("promise2");
  });

console.log("end");
複製代碼

這段程序的輸出是什麼?建議你們先思考一下,最好能動筆畫一畫圖😎。

4.微任務隊列

接着上一節的代碼,符合標準(不少舊版本的瀏覽器實現都是不符合標準的,具體參見參考連接)的輸出應該是:

start
end
promise1
promise2
timeout
複製代碼

可是,爲?什?麼?

實際上,Javascript中有另一種隊列。Promise的回調是被放入這個隊列的。這個隊列叫作microtask queue(微任務隊列),ES6標準中叫Job Queue。microTask的優先級是比task高的,也就是說microtask隊列中的任務要先處理。

  • EventLoop檢測到當前沒有任務在執行,首先檢查microtask隊列中有沒有須要處理的任務。若是有那麼一個個執行,直到microtask隊列爲空。

  • microtask隊列中沒有任務了,執行task隊列中的任務。這裏須要注意,**每執行一個task隊列中的任務,就檢查一下microtask隊列狀態。將microtask隊列中全部任務都執行完成以後,再從task隊列中取出任務執行。

下面咱們看看上面那段代碼是怎麼一步步執行的:

爲方便起見咱們稱setTimeout回調爲timeoutcb,稱第一個then成功回調爲promisecb1,第二個then回調爲promisecb2

[code]                    |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  --------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");        |                   |              | |                   | |               |
  2.                              |                   |              | |                   | |               |
  3. setTimeout(() => {           |                   |              | |                   | |               |
  4.     console.log("timeout");  |                   |              | |                   | |               |
  5. }, 0);                       |                   |              | |                   | |               |
  6.                              |                   |              | |                   | |               |
  7. Promise.resolve()            |                   |              | |                   | |               |
  8. .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1"); |                   |              | |                   | |               |
  10.})                           |                   |              | |                   | |               |
  11..then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); |                   |              | |                   | |               |
  13.});                          |                   |              | |                   | |               |
  14.                             |                   |              | |                   | |               |
  15.console.log("end");          |                   |              | |                   | |               |
複製代碼

開始時,call stack、task queue、microtask queue都爲空。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
> 1. console.log("start");         |     <main>        |              | |                   | |               |
  2.                               |     console.log   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
複製代碼

程序開始運行,壓入<main>函數。首先執行第一行代碼,console.log("start"),將console.log壓棧。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
> 1. console.log("start");         |     <main>        |              | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
複製代碼

console.log執行完成,輸出"start"。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |    <main>         |              | |                   | |               |
  2.                               |    setTimeout     |              | |                   | |               |
> 3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
複製代碼

第二行爲空跳過,開始執行第三行代碼,setTimeout壓棧。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |    <main>         |   timeoutcb <------------------------~~timeoutcb, 0~~|
  2.                               |                   |              | |                   | |               |
> 3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
複製代碼

setTimeout執行完成,因爲超時是0,因此當即回調當即進入task queue中。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |    <main>         |  timeoutcb   | |     promisecb1    | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
> 7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
複製代碼

代碼執行到第7行:

  • 首先Promise.resolve壓棧,執行完成後返回一個Promise對象。

  • 而後調用該對象的then方法,該方法壓棧,執行完成後返回一個全新的Promise對象,咱們稱該對象爲promise1。因爲Promise.resolve返回對象的狀態爲resolved,因此promise1回調直接進入microtask隊列

  • 接着又執行新對象的then方法,咱們稱該對象爲promise2

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |    <main>         |    timeout   | |     promisecb1    | |               |
  2.                               |    console.log    |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
> 15. console.log("end");          |                   |              | |                   | |               |
> start
複製代碼

代碼執行到第15行,console.log壓棧。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |    <main>         |    timeout   | |     promisecb1    | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
> 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
複製代碼

console.log("end")執行完成,輸出"end",出棧。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |                   |    timeout   | |     promisecb1    | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
> 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
複製代碼

<main>函數沒有邏輯須要執行了,出棧。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |    promisecb1     |    timeout   | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
> 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
複製代碼

EventLoop檢查到microtask隊列中有任務須要執行,將promisecb1取出壓入call stack。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |                   |    timeout   | |      promisecb2   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
複製代碼

promisecb1執行完成,輸出"promise1",而且返回undefined。(其實在這裏還有一個console.log壓棧出棧的過程,我就不畫了,下同)

深刻理解Javascript之Promise中看到,若是返回一個值,那麼對象馬上變爲resolved因此第二個then的回調須要安排執行,進入microtask隊列

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |     promisecb2    |    timeout   | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
複製代碼

EventLoop檢測到call stack中沒有正在執行的任務,同時microtask隊列不爲空。從microtask隊列取出任務壓入call stack。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |                   |    timeout   | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
> promise2
複製代碼

promisecb2執行完成,輸出"promise2"。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |    timeoutcb      |              | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
> promise2
複製代碼

接着,EventLoop檢測到call stack和microtask隊列都爲空,從task隊列中取出timeoutcb壓入棧。

[code]                     |   [call stack]    | [task queue] | | [microtask queue] | |   [Web APIs]  |
  ---------------------------------|-------------------|--------------| |-------------------| |---------------|
  1. console.log("start");         |                   |              | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout");   |                   |              | |                   | |               |
  5. }, 0);                        |                   |              | |                   | |               |
  6.                               |                   |              | |                   | |               |
  7. Promise.resolve()             |                   |              | |                   | |               |
  8.  .then(() => {                |                   |              | |                   | |               |
  9.     console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2");  |                   |              | |                   | |               |
  13. });                          |                   |              | |                   | |               |
  14.                              |                   |              | |                   | |               |
  15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
> promise2
> timeout
複製代碼

timeoutcb執行完成,輸出"timeout"。

5.總結

經過這篇文章,咱們瞭解到Javascript時如何經過call stack來處理函數的調用與返回的。setTimeout等異步機制實際上是宿主提供實現,並在異步操做完成負責將回調放入任務隊列,最後由EventLoop在適合的時機取出壓入call stack實際執行。

咱們還看到了另一種隊列——microtask隊列。該隊列中存放的通常是優先級較高的任務,例如Promise的回調處理函數。

每當call stack中沒有正在執行的任務時,EventLoop會優先從microtask隊列中取出任務執行,當該隊列爲空時纔會從task隊列取。

在使用一門框架或語言時,對因而否須要瞭解底層運做機制和原理,每每會有比較大的爭論。有人說,我不瞭解內部原理一樣能夠寫出好程序,那爲何還須要花時間去研究呢? 對此,我以爲了解底層原理仍是很是有必要的。有下面幾個好處:

  • 可讓咱們看到全貌,瞭解整個系統是如何運做的。

  • 底層原理大可能是相通的,例如幾乎全部語言的函數調用底層都是利用CallStack來實現的。學會了Javascript的CallStack運做機制,在學習其餘語言的相關概念時每每能事半功倍。

  • 瞭解底層可讓咱們心中有數,明白什麼事情能作,什麼事情不能作。例如Javascript是單線程的,咱們寫代碼時必定不能讓線程阻塞了😀。

  • 瞭解底層可讓咱們更好的優化代碼。當程序性能出現瓶頸時,能夠更快地定位問題。

6.參考連接

  1. Philip Roberts在歐洲JSConf上的演講(必看)
  2. What is the JS Event Loop and Call Stack? — Jess Telford
  3. Tasks, microtasks, queues and schedules
  4. Understanding the JavaScript call stack

關於我: 我的主頁 簡書 掘金

相關文章
相關標籤/搜索