文章重點:介紹callstack, event loop及和setTimeout的關係
什麼是setTimeout()?
簡單來說,setTimeout這個函式就是一個計時器,時間到之後函式就會開始執行
setTimeout(function(){
console.log(“it’s been 2 sec”)
},2000)
過了兩秒之後函式就會在console.log上面印出我們想要的內容,
syntax: setTimeout(要執行的函式, 等待毫秒數)
setTimeout只會執行一次,如果想重複執行,要使用另一個函式setTimeInterval
接著往下看這個函式,你認為瀏覽器會先印出什麼內容呢?
setTimeout(function(){
console.log(“delay 0 sec”)
},0)console.log("Hey!")
照理來說setTimeout的秒數設定為0,應該要順著程式順序,先印出setTimeout的內容再印出"Hey",但為什麼結果相反呢?
在解釋這個現象為什麼會發生以前,先來認識JS到底是如何運作的吧!
JavaScript引擎
負責以ECMAScript的標準來解析JavaScript並執行它,其中最鼎鼎大名的V8 engine是Google所開發的JavaScript引擎,被Chrome和Node.js所使用。
下圖是JavaScript引擎的運作流程,首先會解析原始碼、直譯、執行等階段,這次我們先專注在Call Stack上,這和本次的主題– event loop有關係
JavaScript引擎是單向執行緒的直譯器,由Call Stack和memory heap所組成
Call Stack 呼叫堆疊
Stack(堆疊)是資料結構的一種,其特色是Last-In-First-Out(後進先出),也就是說最晚放入的資料會最先被取出
Call Stack呼叫堆疊,顧名思義是用來呼叫函式執行的堆疊,前面提到JS是單向執行緒就是因為stack本身的特性--後進先出,最後放入stack的函式會最先被執行,這樣的特性使JS一次只能做一件事情。
在執行一段程式時,callStack會先從memory heap找出要執行的函式,並將它依序推到callStack當中,一旦執行完畢就會將函式從callStack移除,以下用範例說明:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
stack原本是空的,當執行程式時,stack會依序堆疊如下圖:
跟著紅箭頭,隨著函式的執行由下而上依序堆疊,隨著函式執行完畢一個個消除,從multiply, square, printsquare到最後將主程式也消除,清空stack
上述的運行方式會導致什麼問題呢?
很明顯可以看到上述callstack屬於同步(synchronous)執行,一次執行一個事件。如果事件執行的速度很快,倒不會發生什麼問題,但假設我們需要發送api請求抓資料回來後再渲染網頁,若請求等待時間過長,那瀏覽器就一直維持空白,因為在請求尚未完成前,渲染畫面的功能是不會執行的,這種執行方式不僅沒有效率,也會影響使用者體驗。
解決方法:
Asynchronous非同步處理,而本次我們要介紹的非同步函式是setTimeout,也就是文章一開始提到的,這個函式是從webAPI取得的,並非JS內建函式,先來看一個範例:
console.log(“hi”)setTimeout(function cb(){
console.log(“there”)
}, 5000)console.log(“JSConfEU”)
跟上面提到stack的範例一樣,
- 首先主程式會被推到stack
- 接著console.log("Hi")被推到stack,執行完畢後被清除
- 下一個推上來的是setTimeout,這個函式是從webAPI取得的,並非JS engine中的函式,因此會是瀏覽器啟動計時器,這樣子就算是成功呼叫setTimeout,因此setTimeout也被清除
4. 接著console.log("JSConfEU")被推上來,印出來之後被清除,現在主程式已執行完畢,stack全部清空
5. 等計時結束後,瀏覽器就會將回呼函式(callback function)傳給JS,但它不會直接回傳到stack,而是先放入task queue當中,等待event loop運作,event loop會在stack清空時將一項task queue中的任務放到stack當中,這樣的設計是避免非同步的函式隨機被放入stack當中,干擾stack的運作
接著stack就會開始執行callback function,執行後將它消除,這樣整個程式碼就執行完畢了。
從上面例子可以知道,在遇到setTimeout時,它會被瀏覽器處理,等計時完畢再放入task queue等待event loop推到stack
再回到文章一開始提到的例子,就可以知道為什麼印出的結果會先是"Hey"而不是"delay 0 sec"了,因為即使它的倒數時間是0,它依然被放到task queue中,等待stack被清空才能執行,因此順序會在後面。
setTimeout(function(){
console.log(“delay 0 sec”)
},0)console.log("Hey!")
針對文章內部如果有任何問題歡迎留言交流或給予指教!
參考資料
The JavaScript Call Stack — What It Is and Why It’s Necessary