1.JavaScript是一門單執行緒語言,單執行緒意味著他只有一個棧,一次只能去做一件事情,所以要知道他是如何去處理非同步問題還得搞清楚JavaScript的程式碼執行執行機制。
首先我們先寫一段簡單同步執行緒的程式碼
function one(n){console.trace(n);}
function tow(n){one(n)}
function three(n){tow(n)}
three(3)
如圖所示callStack中是正在執行的程式碼,我們使用console.trace()列印此時呼叫站(callStack)的情況,下圖的執行結果讓我們瞭解到函式的呼叫順序是一種後進先出的規律。
2.那麼它是如何解決非同步問題的呢?(task queues)
我們先提一個概念 “任務佇列(task queues)“
由以下程式碼我們可以知道settimeout會延遲一秒執行,他在執行前會被放入一個任務佇列當中(task queues),並不會直接去執行,等待 callStack 中的事件全部完成的時候 event loop才會去將taskQueues中的事件調到callStack中執行
console.log('start'); setTimeout(()=>{ console.log('waiting') } , 1000) console.log('end');
輸出結果 由於setTimeout沒有返回值所以他會先丟擲undefined再等待一秒鐘後列印
3.非同步佇列也有執行優先順序!(Microtasks)
1.macrotask
在等待回撥的任務當中他們也分兩種任務,macrotask queue就是我們常說的task queue。Macrotask包括了setTimeout, Dom 操作 click/onLoad/mouse事件繫結,fetch then response這類操作。實際上這些都是瀏覽器提供的API,所以在執行時是有它們單獨的執行緒去進行操作。舉個例子,setTimeout()設定了2s的延遲,是瀏覽器設定了timer來計時,是另外的執行緒在等待2秒,js主執行緒不受影響,2s後回撥函式再進入task queue。
(function() { console.log('this is the start'); setTimeout(function cb() { console.log('this is a msg from call back'); }); console.log('this is just a message'); setTimeout(function cb1() { console.log('this is a msg from call back1'); }, 0); console.log('this is the end'); })();
event loop會將先有返回的任務放入callstack中 如果cb若和cb1無時間差則按照先進先出的執行順序
2.Microtask
ES6提供了Promise來進行非同步操作。microtask。同上也有一個佇列(job queue)來處理microtask。job queue擁有更高的優先順序。每個task結束後,都會檢查microtask佇列是否有任務在等待執行,根據先進先出,依次執行。
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); });
由於macrotask的佇列(task queues)比 Microtask 的佇列(job queues)優先順序低所以setTImeout的列印內容最後執行
3.總結 event loop
eventloop 當callstack為空時去檢查佇列是否有需要執行的任務,執行順序跟根據先進先出的規則執行,執行的Microtask(Promise) 佇列比macrotask(setTimeout/DOM events/fetch)佇列優先順序高,Microtask 中的任務會先執行,當Microtask中的佇列任務全部執行完畢之後才會去執行macrotask佇列中的任務,當然最大的前提還是callstack得先執行完成
while (queue.waitForMessage()) { queue.processNextMessage(); }