理解同步,非同步和 事件迴圈, 巨集,微任務執行順序
一. 單執行緒
單執行緒,是指在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