JavaScript 概念三明治(五)

同步/非同步

「同步」聽起來就是指一次做好幾件事情,其實並不是,同步指的是:
同一時間只做一件事
套用到JavaScript裡面就是指一次只跑一段程式碼,就像JavaScript語法解析器做的,從上到下一行一行執行那樣。當JavaScript載入全域執行環境,也許呼叫了某個函式創造出一個函式執行環境,然後回到全域環境後結束,這一切都是同步來做處裡的,也就是說,在JavaScript裡面不可能同時執行兩個函式、執行多段程式碼邏輯。
而「非同步」呢?就很好理解了,相較於同步,它的概念則相反:
同一時間內處理不只一件事
必須透過非同步的處理方式,才能讓JavaScript在進入全域執行環境到結束的這中間,能夠額外處理一些需要時間進行的邏輯,而又不影響原來的主程式。在非同步的處理方式下,主程式的運行還是會繼續,而非同步進行的工作也還是會進行,只是不是馬上,而是等到主程式執行完畢,JavaScript引擎有空檔後才會被執行。
非同步的行為在瀏覽器前端很常見,像是部落格網站,當網站一打開,JavaScript必須處理畫面上的一些行為,卻又必須跟後端取文章資料。若以同步的方式來進行這個拉取文章的動作,那麼在拉取道完整文章之前,JavaScript就會無法執行任何其他任務。
也就是說,即便JavaScript是一門同步的語言,但為了符合瀏覽器的使用情境,某些時候它必須具有非同步執行邏輯的能力,至於它怎麼處理這些互動的非同步動作呢?就是透過瀏覽器內的事件迴圈的幫助,關於事件迴圈在後面馬上就會看到。
瀏覽器內可不是只有JavaScript引擎而已,還有其他許多部份在JavaScript引擎之外執行的其他程式,像是渲染引擎負責畫面的構築、以及負責處理網路溝通如Http請求以獲得資料的部分。而JavaScript可能在某些時候必須要要求瀏覽器變更畫面的內容,或是觸發網路溝通的流程,以與其他伺服器溝通,但是這些工作可能會是非同步的,只有在JavaScript引擎裡面事同步的運行。

Event Queue 與 Event Loop

Event Queue、Event Loop這兩個部分並不存在於JavaScript內,而是瀏覽器的其中一部份。它們雖然不屬於JavaScript,卻是前端產品開發裡面最重要的概念之一。因此,需要了解它們為前端互動方面帶來什麼樣的幫助和影響。
>JavaScript引擎的組成

讓我們回想一下前面提到的基礎。之前提到過「執行環境堆疊」,函式呼叫時會產生執行環境,若在這個函式執行環境內含有其他函式被呼叫,就會在之上產生另一個執行環境,形成堆疊。而在上層的執行環境結束之前,下層部分的其他程式碼事無法被執行的,包含全域執行環境。                                                                                                

因此,只要在這之中某個堆疊過久,就算只有一個函式執行環境的堆疊,都有可能影響整個主程式(全域執行環境)的運行。不過應用程式裡面總是會有某些功能需要時間來提取/運算,這時候為了不讓整個主程式停下來等待太久,我們可以,而且其實我們很常把這些比較耗時的工作放在主程式以外的某一個部分去執行。
要討論下一段之前,必須先複習一下,前面我們提到的,JavaScript引擎底下根據功能大致上可以分為三個部分:

#全域執行環境
#執行環境堆疊
#記憶體堆疊

然而瀏覽器內可不只有JavaScript引擎,接下來我們要提到一個很重要的概念--Queue(又稱Message/Event/Callback Queue)。
整個瀏覽器的運行並非只有JavaScript引擎組成。前面說到過,因為JavaSript屬於同步執行的語言,同時又為了讓網頁具有像「監聽事件」、「計時」、「拉第三方API」這些類似「背景作業」的功能,瀏覽器提供了另外一些部份來達成,分別是:
#

#Event Queue
#Web API
#Event Table
#Event Loop

整個由上述部分,包含JavaScript引擎所組成的整體,稱為JavaScript Runtime Environment(JRE),瀏覽器內並不只有JavaScript語言本身而已,我們先來看一下瀏覽器內的事件處理。
>Event Queue
Queue(儲列)是什麼樣的概念呢?我們先來看一下,在寫網頁程式的時候,有一些所謂的「內建的」API如setTimeout、setInterval,這些API不存在於JavaScript原始碼內,但你仍然可以在開發時直接使用。因為這些API是屬於瀏覽器所提供的WebAPI。WebAPI並非JavaScript引擎的一部份,但它屬於瀏覽器運行流程的一環。
關於Web API,舉一些例子:
*操作DOM節點的API:document.getElementeById
*AJAX相關API,像是:XMLttpRequest
*計時類型的API,就像剛剛提到的setTimeout
這類Web API在與JavaScript原始碼一起執行的時候,通常不會直接影響JavaScript主執行環境的運行。否則的話若網頁需要與後端伺服器溝通,拉取外部資料時,就只有乾等,無法執行任何其他事情了!
>Event Queue 運行流程
要了解Event Queue的運行,我們必須先找到一件會放到Event Queue執行的事情。所以這邊要先介紹其中幾個瀏覽器的API:setTimeout與setInterval。setTimeout是一個全域的函式,用於將想做的事情延後幾秒進行。將想要執行的邏輯以函式的方式傳入第一個參數,並在第二個參數傳入想要等待的時間,JavaScript就會為你在若干秒之後,呼叫你傳入的函式。
而setInterval呢?與setTimeout相似,只不過setTimeout只會在你給定的秒數之後執行一次,而setInterval則是根據你給定的時間,固定幾秒執行一次。
function executeAfterDelay(){
console.log("一秒之後才會執行");
}
setTimeout(executerAfterDelay, 1000);
console.log("我會先被被執行");
如果了解了這兩個方法的用途,來觀察上圖的程式碼應該不會太難理解,函式內的程式碼會在一秒之後才被呼叫。不過意外的事情總是會發生,這段程式碼裡面,setTimeout可不像一般的函式那樣運行起來那麼單純。
我們可以試試看把setTimeout的時間調整為0,那是不是就會馬上執行對應的函式內容了呢?
function executeAfterDalay() {
console.log("應該要馬上執行");
}
setTimeout(excuteAfterDalay,0);
console.log("應該要最後執行");

整個流程看起來像這樣:
1.JavaScript 引擎執行到瀏覽器提供的setTimeout函式。
2.JavaScript 引擎繼續運行,同時瀏覽器開始根據給定的秒數計時。
3.等待計時完成後,把剛才接收到的函式推送到Event Queue內。
4.等待JavaScript引擎運行完畢,主執行環境結束後,將Event Queue內的函式推送到JavaScript主執行環境,產生堆疊(執行該函式)。