前言
我們在 Day 6 的時候有談到 JavaScript 是 單執行緒且同步的,是如何同步執行這些程式碼,那我們也有一些疑問仍然有待釐清,像是「非同步回呼 ( asynchronus callbacks)」是什麼意思?
非同步回呼 (asynchronus callbacks)
非同步表示在「同個時間點內不只一個」,可能一段程式在執行時,又開始執行另一段程式碼,然後又執行別的程式碼,這些程式碼在 JavaScript 是同時在執行的。
但我們之前有說過 JavaScript 是「同步」的,並不會「非同步」的執行,它是怎麼樣處理「非同步」事件呢?
首先我們必須知道,瀏覽器內不只有 JavaScript 還有一些其它像是Rendering 、HTTP Request …等等,在 JavaScript 外執行別的程式。JavaScript 可以向 Rendering 溝通來改變網頁的樣子,或者向HTTP Request 請求資料。但這些可能都是非同步執行,表示 JavaScript 的請求在瀏覽器內是「非同步」的,裡面只有 JavaScript 本身是「同步」的。
克服 JS 奇怪部分 截圖
所以當我們「非同步」向外請求,或是某人點擊了一個按鈕執行了某個函式時,這些被「非同步」處理的事情,會怎麼樣?
還記得我們之前提的「執行堆」嗎?在 JavaScript 內的等待列則稱為「事件佇列 (Event Queue)」這裡面都是事件、事件通知,這些可能要發生的東西。所以當瀏覽器在 JavaScript 外的某處有個需要被通知的事件時,會將之放進佇列裡,這個事件可能需要有個函式來回應它,我們可以監聽這個事件,並且處理它。
要特別注意的是,當執行堆是空的 JavaScript 才會注意事件佇列,在執行堆還沒空之前,是不會處理事件佇列的。
在處理事件佇列時,JavaScript 會看是否有函式被這個 CLICK 事件觸發,處理後知道有一個函式需要執行,因此又創造執行環境給那個函式,並加入執行堆,接著處理完畢後,又繼續到下一個佇列的事件,如此循環。
也就是說,這不是真正的「非同步」,而是瀏覽器「非同步」的把東西放到事件佇列,但原本 JavaScript 的程式仍然一行行執行,當執行後、執行堆空了、執行環境清除了,才開始處理事件佇列內的事件,周而復始。
來點範例:
1 | function wait(){ |
我們寫了兩個函式,一個用來模擬需要花長時間動作的函式,結束後會印出函式結束的字樣,另一個用來監聽出現在事件佇列的瀏覽器的點擊事件。我們透過這個範例來觀察,如果我重新整理網頁後,當 wait
函式仍執行時,點擊畫面 / 不點擊畫面,這些 console.log
的輸出會是如何,是不是如我們上面所說的一樣。
當 wait 函式仍執行時,不點擊畫面
當 wait 函式仍執行時,點擊畫面
透過以上觀察,我們可以得知,當執行堆是空的 JavaScript 才會開始處理事件佇列內的事件,這也表示執行長時間的函式,可以干擾事件
這就是 JavaScript 如何用「同步」的方式處理在瀏覽器別處「非同步」的事件,當事件佇列都結束了之後, JavaScript 會繼續看事件佇列的迴圈,這稱為「持續檢查 (Continuous Check)」,當事件再度出現在事件佇列,就會執行。
也就是說非同步回呼在 JavaScript 是可能的,但是非同步的部分,是發生在JavaScript 之外。JavaScript透過事件佇列迴圈,當執行堆沒有東西時,就開始處理事件佇列內的事件,但是是「同步」的處理。