題目背景
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log( `async2`);
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log(`script end`);
複製程式碼
題目的本質,是考察setTimeout
、promise
、async await
的實現順序及JS的時間迴圈方面的相關問題。
結果為:
script start async1 start async2 promise1 script end async1 end promise2 settimeout
這裡涉及到同步非同步Microtasks
和Macrotasks
。 Microtasks
優先順序別高於Macrotasks
其中Microtasks
、Macrotasks
分別是:
microtasks:
- process.nextTick
- promise
- Object.observe
- MutationObserver
macrotasks:
- setTimeout
- setInterval
- setImmediate
- I/O
- UI渲染
- 一個事件迴圈(event loop)會有一個或多個任務佇列(task queue)
- task queue 就是 macrotask queue
- 每一個 event loop 都有一個 microtask queue
- task queue == macrotask queue != microtask queue
- 一個任務 task 可以放入 macrotask queue 也可以放入 microtask queue 中
因此事件迴圈的順序,決定了JavaScript程式碼的執行順序。它從script(整體程式碼)開始第一次迴圈。之後全域性上下文進入函式呼叫棧。直到呼叫棧清空(只剩全域性),然後執行所有的micro-task。當所有可執行的micro-task執行完畢之後。迴圈再次從macro-task開始,找到其中一個任務佇列執行完畢,然後再執行所有的micro-task,這樣一直迴圈下去。
定時器是怎麼實現定時的?為什麼會出現不定時的情況?
首先,要明確的一點:javascript是以單執行緒的方式執行的。JavaScript的主要用途是與使用者互動,以及操作DOM。若以多執行緒的方式,則可能出現衝突。假設有兩個執行緒同時操作一個DOM元素,執行緒1要求瀏覽器刪除DOM,而執行緒2卻要求修改DOM樣式,這時瀏覽器就無法決定採用哪個執行緒的操作。當然,我們可以為瀏覽器引入“鎖”的機制來解決這些衝突,但大大提高複雜性,所以 JavaScript從誕生開始就選擇了單執行緒執行。在某一時刻內只能執行特定的一個任務,並且會阻塞其它任務執行。
但是JavaScript 有個基於“Event Loop”併發的模型(不是並行)。前者是邏輯上的同時發生,而後者是物理上的同時發生。所以,單核處理器也能實現併發。
上圖說明一下併發和並行:
小tips:
console.log(1);
setTimeout(function(){
console.log(2);
Promise.resolve(1).then(function(){
console.log(`ok`)
})
})
setTimeout(function(){
console.log(3)
})
複製程式碼
分析:先預設走棧,輸出1。此時並沒有微任務,所以微任務不會執行。先走第一個setTimeout,輸出2,同時將微任務放到佇列中,執行微任務,輸出ok,微任務執行完,再走巨集任務,輸出3。
Node.js的Event Loop
V8引擎解析JavaScript指令碼。
解析後的程式碼,呼叫Node API。
libuv庫負責Node API的執行。它將不同的任務分配給不同的執行緒,形成一個Event Loop(事件迴圈),以非同步的方式將任務的執行結果返回給V8引擎。
V8引擎再將結果返回給使用者。