1. 程式人生 > >JavaScript的執行機制Event Loop(事件迴圈)

JavaScript的執行機制Event Loop(事件迴圈)

我們都知道JavaScript是一個單執行緒的語言。單執行緒也就意味著同一時間只能做一件事。
JavaScript之所以設計為單執行緒,是因為它的用途主要還是用來操作DOM,為了避免複雜性,所以JavaScript在誕生之初就是單執行緒的,這也是這麼語言的核心特徵,未來也不會改變。
雖然HTML5為了儘可能地利用CPU的計算能力而推出了Web Worker標準,允許JavaScript指令碼建立多個執行緒,但是需要注意的是子執行緒必須受主執行緒的控制,並且還不能操作DOM。所以,這個標準並沒有改變JavaScript的單執行緒本質。

任務佇列

單執行緒就意味著所有任務都需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。

但是這種效率很低,所以JavaScript的任務分成兩種,一種是同步任務(synchronous),另一種是非同步任務(asynchronous)。同步任務指的是,在主執行緒上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;非同步任務指的是,不進入主執行緒、而進入’任務佇列’(task queue)的任務,只有’任務佇列’通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。

執行過程如下:

  1. 所有同步任務都在主執行緒執行,形成一個執行棧.
  2. 所有非同步任務都在任務佇列中(task queue) 。只要非同步任務有了執行結果,就在‘任務佇列’之中放置一個事件.(這個事件是一個flag)
  3. 等執行緒執行完執行棧中的同步任務之後,系統會讀取‘任務佇列’,看看裡面有哪些事件,然後把等待執行的任務(有flag的任務)推入到執行棧中,開始執行。
  4. 主執行緒不斷重複第三步。

    這裡寫圖片描述

只要主執行緒空了,就會讀取‘任務佇列’。這就是js的執行機制。

事件和回撥函式

任務佇列其實是一個事件佇列,因為裡面的非同步任務在有了執行結果(IO裝置,使用者產生的事件包括click等,ajax,定時器)之後,就會在‘任務佇列’中新增一個事件,表示相關的非同步任務可以進入‘執行棧’了。主執行緒讀取非同步佇列,其實就是讀取裡面的事件。

非同步任務必須指定回撥函式,ES7的async,await 其實也是指定了回撥函式的,只不過是用同步寫法寫非同步。
‘任務佇列’是一個先進先出的資料結構,排在前面的事件,優先被主執行緒讀取。
再次強調的是,是排在前面的事件,而不是排在前面的非同步任務,因為主執行緒讀取的是事件。
看個例子:

console.log(1)
setTimeout(function(){console.log(2)}, 5)
setTimeout(function(){console.log(3)}, 4);
console.log(4);

如果讀取的是非同步任務的話,那麼應該是1,4,2,3
而結果是1,4,3,2
因為非同步佇列中的兩個任務,第二個任務先有執行結果,先獲得flag事件。所以排在task queue的前面,先被讀取到。

另外需要注意的是,對於setTimeout這類定時器只是將事件插入到了‘任務佇列’裡,必須要等到執行棧中的程式碼執行完之後,主執行緒才會執行它指定的回撥函式。所以說如果執行棧中的程式碼耗時很長,就有可能等很久,所以沒辦法保證回撥函式一定會在setTimeout()指定的時間執行。

Event Loop

主執行緒中‘任務佇列’中讀取事件,這個過程是迴圈不斷的。所以這種執行機制叫做Event Loop(時間迴圈)。

這裡寫圖片描述