1 | setTimeout(function(){ |
在上述的程式碼中,可以發現順序較前的’‘setTimeout’‘卻晚於’‘Hello!’'執行,原因在於JavaScript的event loop。我們先探討JavaScript的幾個特性。
單執行緒
因為JavaScript的處理程序為單執行緒,意思為同一時間僅能處理一件事,要處理完正在處理的程序才能處理下一件事,而當某程序耗費較長的時間時,會導致後面的程序因為該程序而無法繼續進行,也就是所謂的「阻塞」。
JavaScript 引擎
Google’s V8是流行的Javascript引擎之一,它使用在Chrome瀏覽器和Node.js中,V8引擎主要包含兩部分:
- Memory Heap ( 內存堆 ) — 分配內存將會在這裡發生,以無序的方式儲存任何類型的任意數據
- Call Stack ( 堆疊 ) — > 引擎跟蹤 code 執行位置的地方,從命名上可知它的資料結構為Stack,資料的進出是以先進後出的方式。
Call Stack ( 堆疊 )
我們來看一段code:
1 | function func1() { |
執行結果:
從以上可以得知,當準備執行func1時,func1被放進了Call Stack,而因為func1呼叫了func2,func2被放到了func1的上方,而當func2執行完畢後(印出func2 end),func1被移出Stack,接著func1執行完畢,換func1被移出Stack,運作過程如下:
那所以這和我們最一開始提到的現象有什麼關係呢?
有一些APIs並非是由JavaScript 引擎提供的,像是DOM、AJAX、setTimeout等,這些是瀏覽器提供的,也就是WEB APIs。而JavaScript的call stack遇到這些APIs時,會先丟到網頁讓它處理。
任務佇列(task quene)
而這些網頁處理完的APIs要放到哪呢?
為了避免一處理完丟回call stack造成程序混亂,在處理完WEB APIs後會先丟到task quene的地方,等到call stack處理完後才會傳回task quene進行處理。
事件循環(event loop)
這時候就輪到event loop上場啦,它會一直去查看stack裡面是不是還有東西,如果是空了的話,便會把quene排序第一的任務放到stack裡去執行。
所以在一開始提到的例子中,當javascript引擎遇到setTimeout時,它會判斷它不是JS引擎的功能,於是把它丟到瀏覽器讓瀏覽器處理,接著繼續執行stack接下來的任務,也就是console.log(‘Hello!’);同時瀏覽器也在處理setTimeout,並將處理完的任務放到quene裡,這也是JS所謂的非同步處理(asynchronous)。而當stack空了的時候,event loop便會將quene裡的console.log(‘delay 0 sec’)丟回stack,因此結果才會是先印出"Hello"之後才印出"delay 0 sec"。