JS事件迴圈Event Loop

舞動乾坤發表於2019-02-19
上週寫了篇關於setTimeout的文章,其實也就牽扯到了js的執行機制。所以,這周就來談談javascript的執行機制吧。

那就先問個問題吧?。

為什麼JavaScript是單執行緒的?

還是我來回答吧:

單執行緒意思就是說同一個時間只能做一件事。那這樣的話效率不是很低?也沒有啦,其實javascript的單執行緒特點是跟他的用途有關的。作為瀏覽器指令碼語言,JavaScript的主要用途是與使用者互動,以及操作DOM。假如不是單執行緒的話,在一個執行緒當我們在給某個DOM節點增加內容的時候,另一個執行緒正在刪除這個DOM節點的內容,那還得了,那不是亂套了嗎。所以javascript只能是單執行緒。

雖然javascript是單執行緒,但是javascript中有同步和非同步的概念,解決了js阻塞的問題。

同步和非同步:

一.同步:

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

用程式碼解釋一下:

console.log(`Hello`);
複製程式碼

如果在函式返回時,就看到了預期的效果:在控制檯列印了Hello

二.非同步:

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

程式碼解釋:

fs.readFile(`test.txt`, `utf8`, function(err, data) {
    console.log(data);
});
複製程式碼

在上面的程式碼中,我們希望通過fs.readFile函式讀取檔案foo.txt中的內容,並列印出來。但是在fs.readFile函式返回時,我們期望的結果並不會發生,而是要等到檔案全部讀取完成之後。如果檔案很大的話可能要很長時間。

小總結:

同步方法呼叫一旦開始,呼叫者必須等到方法呼叫返回後,才能繼續後續的行為。

非同步方法呼叫更像一個訊息傳遞,一旦開始,方法呼叫就會立即返回,呼叫者就可以繼續後續的操作。而,非同步方法通常會在另外一個執行緒中,“真實”地執行著。整個過程,不會阻礙呼叫者的工作。

javascript就可以進行同步任務和非同步任務。把讀檔案這種操作,ajax請求這些需要耗時的任務放到任務佇列中,我還是能夠一步步的繼續下面的任務。所以啊,javascript還是可以很6。那麼非同步任務裡面只是放要進行非同步操作的任務嗎,裡面會發生啥呢?

任務佇列:

上面說過了javascript裡面的任務有兩種,同步任務和非同步任務。

同步任務是指:在主執行緒上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務。

非同步任務指的是,不進入主執行緒、而進入”任務佇列”的任務,只有”任務佇列”通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。

先看個小栗子吧:

console.log("a");
setTimeout(function () {
    console.log("b");
},0);
console.log("c");

//a
//c
//b
複製程式碼

js中程式碼從上往下執行,執行第一行程式碼的時候控制檯輸出a,執行到第二行程式碼的時候遇到了setTimeout函式,因為setTimeout函式是個非同步函式,所以,瀏覽器會記住這個事件,新增到時間表中,之後把這個事件的回撥函式入棧到任務佇列中。而此時主執行緒程式繼續往下執行,到了第五行:console.log(“c”),執行這條,控制檯輸出c。這時候主執行緒空了,他會到任務佇列裡面去查詢是否有可以執行的任務,有的話直接拿出來執行,沒有的話會一直去詢問,等到有可以執行的。

為了更好的說明任務佇列和事件迴圈,先看一張圖。?

![](data:image/svg+xml;utf8,<?xml version=”1.0″?><svg xmlns=”http://www.w3.org/2000/svg” version=”1.1″ width=”1280″ height=”970″></svg>)
這張圖片裡面已經畫出了js的事件迴圈的流程了。
流程:

  1. 所有同步任務都在主執行緒上執行,形成一個執行棧。
  2. 當主執行緒中的執行棧為空時,檢查事件佇列是否為空,如果為空,則繼續檢查;如不為空,則執行3;
  3. 取出任務佇列的首部,壓入執行棧;
  4. 執行任務;
  5. 檢查執行棧,如果執行棧為空,則跳回第 2 步;如不為空,則繼續檢查;

Event Loop:

事件迴圈其實就是入棧出棧的迴圈。上面例子中說到了setTimeout,那setInterval呢,Promise呢等等等等,有很多非同步的函式。但是這些非同步任務有分巨集任務(macro-task)和微任務(micro-task):

macro-task包括: setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。

每一次Event Loop觸發時:

  1. 執行完主執行執行緒中的任務。
  2. 取出micro-task中任務執行直到清空。
  3. 取出macro-task中一個任務執行。
  4. 取出micro-task中任務執行直到清空。
  5. 重複3和4。
其實promise的then和catch才是microtask,本身的內部程式碼不是。

注意:

在瀏覽器瀏覽器和node中的執行不一樣。

任務佇列裡面是“先入先出”的。

那來個小栗子測試一下你是不是已經完全理解啦:
console.log(`global`)

for (var i = 1;i <= 5;i ++) {
  setTimeout(function() {
    console.log(i)
  },i*1000)
  console.log(i)
}

new Promise(function (resolve) {
  console.log(`promise1`)
  resolve()
 }).then(function () {
  console.log(`then1`)
})

setTimeout(function () {
  console.log(`timeout2`)
  new Promise(function (resolve) {
    console.log(`timeout2_promise`)
    resolve()
  }).then(function () {
    console.log(`timeout2_then`)
  })
}, 1000)
複製程式碼

控制檯輸出:
![](data:image/svg+xml;utf8,<?xml version=”1.0″?><svg xmlns=”http://www.w3.org/2000/svg” version=”1.1″ width=”668″ height=”273″></svg>)
這個部落格能夠非常好的加深理解:深入理解 JavaScript 事件迴圈(一)— event loop

總結:

這裡主要是講了在瀏覽器端js事件迴圈。這篇文章可以幫助更好的理解node和瀏覽器環境下不同的事件迴圈:瀏覽器和Node不同的事件迴圈(Event Loop)

相關文章