JavaScript-事件迴圈-eventLoop

mazy發表於2018-07-03

JavaScript-事件迴圈-eventLoop

首先看三個問題

  1. 定時器真的是定時執行的嗎?
  2. 定時器的回撥函式是在分執行緒上執行的嗎?
  3. 定時器是如何執行的呢?

先來看一段程式碼

document.getElementById('btn').onclick = function(){
    var start = Date.now()
    console.log('定時執行開始')
    setTimeout(function(){
        console.log('定時執行中')
        console.log('定時執行了 ', Date.now() - start)
    },200)
    console.log('定時執行結束')
}
複製程式碼

再提問題

  1. 這幾個console.log 列印的順序是什麼樣的?
  2. 定時器執行了多長時間?

JavaScript-事件迴圈-eventLoop

好繼續

document.getElementById('btn').onclick = function(){
    var start = Date.now()
    console.log('定時執行開始')
    setTimeout(function(){
        console.log('定時執行中')
        console.log('定時執行了 ', Date.now() - start)
    },200)
    console.log('定時執行結束')
    //一個很長時間的操作
    for (var i = 0; i < 10000000000; i++){}
}
複製程式碼

繼續提問題

  1. 這時候定時器執行了多長時間呢?

JavaScript-事件迴圈-eventLoop

好,回答上面第一個問題

  1. 通過上面的程式碼發現,定時器並不能保證真正的定時執行
  2. 正常來說定時器會延遲一點點,這個我們是可以接受的,
  3. 但是有的時候會延遲很長時間,這個我們完全接受不了

繼續看一段程式碼

setTimeout(function(){
    console.log('2s後執行')
},2000)
setTimeout(function(){
    console.log('1s後執行')
},1000)
setTimeout(function(){
    console.log('0s後執行')
},0)
function fn(){
    console.log('fn')
}
fn()
console.log('alert 之前')
alert('暫停主執行緒')
console.log('alert 之後')
複製程式碼

請問執行結果是什麼樣的?

JavaScript-事件迴圈-eventLoop

JavaScript-事件迴圈-eventLoop

在這裡發現了點端倪

  1. 先列印了fn()
  2. 列印了alert之前
  3. 然後執行了alert,因為alert有暫停主執行緒的功能,同時也能暫停計時
  4. 點選了確定才列印alert之後,然後0s後執行,1s後列印1s後執行...

通過這個操作發現了點問題

這塊就有個程式碼分類的問題:

  1. 初始化程式碼
  2. 回撥程式碼

js引擎執行流程

  1. 首先會先執行初始化程式碼包括(設定定時器,繫結監聽,傳送ajax...)
  2. 然後在後面的某個時刻才會執行回撥程式碼

回答第二個問題

如何證明js執行時單執行緒的呢?

剛才執行的程式碼,以及列印的結果可以發現,程式碼的執行順序都是沿著一條線走下來的,alert有暫停主執行緒的功能,當暫停的時候,setTimeout定時裡面的程式碼並沒有執行,哪怕過上一天,只要不點確定按鈕,他就不會執行,當點選了確定按鈕,發現定時器按照設定好的時間間隔進行執行列印,最奇怪的就是0s執行為什麼也是在alert之後列印,因為回撥函式屬於回撥程式碼,只有初始化程式碼執行完之後才會執行回撥程式碼

為什麼js要用單執行緒模式

JavaScript的單執行緒,與它的用途有關。作為瀏覽器指令碼語言,JavaScript的主要用途是與使用者互動,以及操作DOM。這決定了它只能是單執行緒,否則會帶來很複雜的同步問題

事件驅動模型

JavaScript-事件迴圈-eventLoop

從上面圖發現分為三部分& 回答第三個問題

  1. js引擎的堆疊模組
  2. 瀏覽器負責的WebAPIs模組=> 事件管理模組
  3. 還有一個回撥函式佇列

執行順序

  1. 首先會先執行執行棧中的操作,也就是stack中的操作
  2. 事件管理模組會將dom事件(onClick...),ajax,setTimeout事件的回撥函式新增進回撥佇列中(當延時方法到達觸發條件,即到達設定的延時時間時,這一延時方法被新增至任務佇列裡)
  3. 執行引擎在主執行緒方法(執行棧)執行完畢,到達空閒狀態時,會從回撥佇列中順序獲取任務來執行,這一過程是一個不斷迴圈的過程

任務佇列

單執行緒就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。 所有任務可以分成兩種,一種是同步任務,另一種是非同步任務。同步任務指的是,在主執行緒上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;非同步任務指的是,不進入主執行緒、而進入"任務佇列"(task queue)的任務,只有"任務佇列"通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。 具體來說,非同步執行的執行機制如下。(同步執行也是如此,因為它可以被視為沒有非同步任務的非同步執行。)

  1. 所有同步任務都在主執行緒上執行,形成一個執行棧(execution context stack)。
  2. 主執行緒之外,還存在一個"任務佇列"(task queue)。只要非同步任務有了執行結果,就在"任務佇列"之中放置一個事件。
  3. 一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務佇列",看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行。
  4. 主執行緒不斷重複上面的第三步。
  5. 只要主執行緒空了,就會去讀取"任務佇列",這就是JavaScript的執行機制。這個過程會不斷重複。
  6. 所謂"回撥函式",就是那些會被主執行緒掛起來的程式碼。非同步任務必須指定回撥函式,當主執行緒開始執行非同步任務,就是執行對應的回撥函式。
  7. "任務佇列"是一個先進先出的資料結構,排在前面的事件,優先被主執行緒讀取。主執行緒的讀取過程基本上是自動的,只要執行棧一清空,"任務佇列"上第一位的事件就自動進入主執行緒。但是,由於存在後文提到的"定時器"功能,主執行緒首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主執行緒。

相關文章