1. 程式人生 > 其它 >理解同步,非同步和 事件迴圈, 巨集,微任務執行順序

理解同步,非同步和 事件迴圈, 巨集,微任務執行順序

一. 單執行緒

單執行緒,是指在JS引擎中負責解釋和執行JavaScript程式碼的執行緒只有一個 ,不妨叫它主執行緒

但是實際上還存在其他的執行緒。例如:處理AJAX請求的執行緒、處理DOM事件的執行緒、定時器執行緒、讀寫檔案的執行緒(例如在Node.js中)等等。這些執行緒可能存在於JS引擎之內,也可能存在於JS引擎之外,在此我們不做區分。不妨叫它們工作執行緒。

js是單執行緒的,一個任務完成後才能執行另一個

二. 同步和非同步

假設存在一個函式A:

A(args...);
同步:如果在函式A返回的時候,呼叫者就能夠得到預期結果(即拿到了預期的返回值或者看到了預期的效果),那麼這個函式就是同步的。

例如:

Math.sqrt(2);
console.log('Hi');
  • 第一個函式返回時,就拿到了預期的返回值:2的平方根。

  • 第二個函式返回時,就看到了預期的效果:在控制檯列印了一個字串。

所以這兩個函式都是同步的。

非同步:如果在函式A返回的時候,呼叫者還不能夠得到預期結果,而是需要在將來通過一定的手段得到,那麼這個函式就是非同步的。

例如:

fs.readFile('foo.txt', 'utf8', function(err, data) {
    console.log(data);
});

在上面的程式碼中,我們希望通過fs.readFile函式讀取檔案foo.txt中的內容,並打印出來。
但是在fs.readFile函式返回時,我們期望的結果並不會發生,而是要等到檔案全部讀取完成之後。如果檔案很大的話可能要很長時間。
下面以AJAX請求為例,來看一下同步和非同步的區別:

非同步AJAX:

  • 主執行緒:“你好,AJAX執行緒。請你幫我發個HTTP請求吧,我把請求地址和引數都給你了。”

  • AJAX執行緒:“好的,主執行緒。我馬上去發,但可能要花點兒時間呢,你可以先去忙別的。”

  • 主執行緒::“謝謝,你拿到響應後告訴我一聲啊。”

(接著,主執行緒做其他事情去了。一頓飯的時間後,它收到了響應到達的通知。)

從上文可以看出,非同步函式實際上很快就呼叫完成了。但是後面還有工作執行緒執行非同步任務、通知主執行緒、主執行緒呼叫回撥函式等很多步驟。我們把整個過程叫做非同步過程。非同步函式的呼叫在整個非同步過程中,只是一小部分。

總結一下,一個非同步過程通常是這樣的:

主執行緒發起一個非同步請求,相應的工作執行緒接收請求並告知主執行緒已收到(非同步函式返回);
主執行緒可以繼續執行後面的程式碼,同時工作執行緒執行非同步任務;
工作執行緒完成工作後,通知主執行緒;主執行緒收到通知後,執行一定的動作(呼叫回撥函式)。

非同步函式通常具有以下的形式:

A(args..., callbackFn)
它可以叫做非同步過程的發起函式,或者叫做非同步任務註冊函式。args是這個函式需要的引數。callbackFn也是這個函式的引數,但是它比較特殊所以單獨列出來。

所以,從主執行緒的角度看,一個非同步過程包括下面兩個要素:

  • 發起函式(或叫註冊函式)A

  • 回撥函式callbackFn

它們都是在主執行緒上呼叫的,其中註冊函式用來發起非同步過程,回撥函式用來處理結果。

三. 訊息佇列和事件迴圈

上文講到,非同步過程中,工作執行緒在非同步操作完成後需要通知主執行緒。那麼這個通知機制是怎樣實現的呢?答案是利用訊息佇列和事件迴圈。
用一句話概括:

工作執行緒將訊息放到訊息佇列,主執行緒通過事件迴圈過程去取訊息。

  • 訊息佇列:訊息佇列是一個先進先出的佇列,它裡面存放著各種訊息。

  • 事件迴圈:事件迴圈是指主執行緒重複從訊息佇列中取訊息、執行的過程。
    例:

setTimeout(() => {
            console.log('hello');
        }, 1000);

1秒後,console.log('hello');被放在任務佇列(訊息佇列)裡
當主執行緒執行完任務後會從訊息佇列中拿任務在執行,
主執行緒只有在將當前的訊息執行完成後,才會去取下一個訊息。這種機制就叫做事件迴圈機制,取一個訊息並執行的過程叫做一次迴圈。

實際上,主執行緒只會做一件事情,就是從訊息佇列裡面取訊息、執行訊息,再取訊息、再執行。

四.巨集任務,微任務以及執行順序

非同步任務:

  • 計時器
  • ajax
  • 讀取檔案
    同步程式執行完成後在執行非同步任務

巨集任務:

  • 計時器 ajax 讀取檔案

微任務:

  • promise.then()

執行順序:

  • 1.同步任務
  • 2.process.nextTick
  • 3.微任務
  • 4.巨集任務
  • 5.setImmeduate