iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 8
0

我們已經討論過 JavaScript 的同步性(逐字逐行執行)

那非同步回呼(asynchronus callbacks)是什麼?

非同步表示在一個時間點不只執行一段程式,
可能有一段程式在執行時會開始執行另一段程式碼,然後又會再執行別的程式碼,
這些程式碼在 JavaScript 引擎內是同時在執行的,
但之前說過 JavaScript 是同步的,
它不會非同步的執行,
它一次執行一行程式碼,

首先我們需要思考 JavaScript 引擎本身,
例如瀏覽器裡面還有其他東西,
有其他引擎,在 JavaScript 引擎外執行別的程式,
像是呈現引擎,可以呈現或印出或畫出網頁的東西到螢幕,
也有東西處理瀏覽器的回應 HTTP 的請求, 例如獲得資料,
JavaScript 引擎可以和呈現引擎溝通,
改變網頁的樣子 也可以請求資料,
表示呈現引擎和請求在瀏覽器內是非同步執行的,
裡面只有 JavaScript 引擎是同步的


圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

讓我們看一下,
我們已經學了執行堆,
當函數被呼叫時這些執行環境被創造,
它會在執行堆最上面,

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

函數執行結束後,離開執行堆,

然而 JavaScript 引擎內的等待列則稱為事件佇列(event queue),
這裡面都是事件、事件通知,這些可能要發生的,
所以當瀏覽器,在 JavaScript 引擎外的某處,有一個需要被通知的事件,
在 JavaScript 引擎裡會被放到佇列裡,
我們可能有函數需要回應它,
我們可以聆聽這個事件,然後用函數處理事件,
事件會被放到佇列上,
所以例如這個 click 事件,如果有人在螢幕上點擊,
如果我有個回應 click 事件的函數 或可能有另一個正在執行的程式碼,像是取得資料,
在瀏覽器得到資料後,我的程式繼續執行,
現在程式停止了,當執行堆是空的 JavaScript 才會注意事件佇列,


圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

所以我的 b 函數執行完畢,離開執行堆上方,

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

現在回到執行 a 函數,然後將 a 函數執行完,

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

然後回到全域執行環境,將剩下的程式碼執行完,

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

然後當執行堆是空的,


圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

JavaScript 會注意事件佇列,
它預期那邊會有東西,
如果有,它會看是否有函數會被這個事件觸發,
所以它看到 click 事件,處理 click 事件之後知道有一個函數需要執行,
所以在事件發生時,它會創造執行環境給那個函數,

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

然後這個事件就處理完畢,
繼續到下一個佇列的事件,

圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖

如此循序將事件佇列中的事件執行下去,
這稱為持續檢查(continuous check),

再說一次,
事件佇列直到執行堆是空的才會處理,
直到 JavaScript 已經逐行執行完程式,
所以這不是真正的非同步,
而是瀏覽器非同步的把東西放到事件佇列,
但原本的程式仍然繼續一行行執行,
當執行完後,執行堆空了,執行環境清除了 都結束後,才會處理事件,
它等待這些事件,
如果事件觸發函數,
會出現在執行堆,就像一般情況一樣執行,

來看看一些程式碼,
程式碼如下:

// long running function
function waitThreeSeconds() {
  var ms = 3000 + new Date().getTime();
  while(new Date() < ms) {}
  console.log('finished function');
}

function clickHandler() {
  console.log('click event be emit!');
}

// listen for the click event
window.addEventListener('click', clickHandler, false);

waitThreeSeconds();
console.log('finished execution');

如果我在網頁剛開始載入就點擊瀏覽器,
這3個 console.log 的結果在 Chrome 的 Console 中出現的順序為何?

我們直接來看:

跟你想的一樣嗎?

因為所有的函數會先被執行,當執行堆被清空, JavaScript 才會將事件佇列裡的事件處理函數放進執行堆中執行,
這表示長時間函數可以干擾事件,

再說一次,非同步回呼在 JavaScript 是可能的,
但非同步的部分是發生在 JavaScript 引擎外(瀏覽器的事件佇列裡),

JavaScript 透過這個事件迴圈,透過這個事件佇列,
當它準備好時會看看事件,然後處理它們,但是同步的處理,
在執行堆中一次執行一個處理事件的函數。


上一篇
Day 7 範圍鏈
下一篇
Day 9 JavaScript 型別裡的純值
系列文
教練我想學 JavaScript 30

尚未有邦友留言

立即登入留言