我是在作前端面試題中看到了setTimeout和Promise的比較,而後第一次看到了microtask和macrotask的概念,在閱讀了一些文章以後發現沒有一個比較全面易懂的文章,因此我嘗試作一個梳理性的總結.javascript
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');
首先咱們先弄清楚setTimeout和Promise的共同點,也就是我第一次的看到那道面試題的疑惑點.html
JavaScript 主線程擁有一個 執行棧 以及一個 任務隊列,主線程會依次執行代碼,當遇到函數時,會先將函數 入棧,函數運行完畢後再將該函數 出棧,直到全部代碼執行完畢。前端
上面的例子的執行棧執行順序應該是這樣的java
console.log('script start'); console.log('script end'); Promise.resolve();
而任務隊列的執行順序應該是這樣的git
Promise.then(function() { console.log('promise1'); }); Promise.then(function() { console.log('promise2'); }); setTimeout(function() { console.log('setTimeout'); }, 0);
而主線程則會在 清空當前執行棧後,按照先入先出的順序讀取任務隊列裏面的任務。github
衆所周知setTimeout和Promise.then()都屬於上述異步任務的一種,那到底爲何setTimeout和Promise.then()會有順序之分,這就是我想分析總結的問題所在了.web
tasks的做用是爲了讓瀏覽器可以從內部獲取javascript / dom的內容並確保執行棧可以順序進行。面試
tasks的調度是隨處可見的,例如解析HTML,得到鼠標點擊的事件回調等等,在這個例子中,咱們所迷惑的setTimeout也是一個tasks.api
microtasks一般用於在當前正在執行的腳本以後直接發生的事情,好比對一系列的行爲作出反應,或者作出一些異步的任務,而不須要新建一個全新的tasks。promise
只要執行棧沒有其餘javascript在執行,在每一個tasks結束時,microtasks隊列就會在回調後處理。在microtasks期間排隊的任何其餘microtasks將被添加到這個隊列的末尾並進行處理。
microtasks包括mutation observer callbacks,就像上例中的promise callbacks同樣。
須要注意的是,在兩個tasks之間,瀏覽器會從新渲染。這也是咱們須要瞭解tasks和microtasks的一個很是重要的緣由.
Vue 中如何使用 MutationObserver 作批量處理? - 顧軼靈的回答 - 知乎根據 HTML Standard,在每一個 task 運行完之後,UI 都會重渲染,那麼在 microtask 中就完成數據更新,當前 task 結束就能夠獲得最新的 UI 了。反之若是新建一個 task 來作數據更新,那麼渲染就會進行兩次。
在__Microsoft Edge__, Firefox 40__, __iOS Safari 以及 desktop Safari 8.0.8 中setTimeout會先於Promise
//html <div class="outer"> <div class="inner"></div> </div>
// Let's get hold of those elements var outer = document.querySelector('.outer'); var inner = document.querySelector('.inner'); // Let's listen for attribute changes on the // outer element new MutationObserver(function() { console.log('mutate'); }).observe(outer, { attributes: true }); // Here's a click listener… function onClick() { console.log('click'); setTimeout(function() { console.log('timeout'); }, 0); Promise.resolve().then(function() { console.log('promise'); }); outer.setAttribute('data-random', Math.random()); } // …which we'll attach to both elements inner.addEventListener('click', onClick); outer.addEventListener('click', onClick);
在這個例子中,不一樣瀏覽器的log是不一樣的,以下所示
Chrome | Firefox | Safari | edge |
---|---|---|---|
click | click | click | click |
promise | mutate | mutate | click |
mutate | click | click | mutate |
click | mutate | mutate | timeout |
promise | timeout | promise | promise |
mutate | promise | promise | timeout |
timeout | promise | timeout | promise |
timeout | timeout | timeout | \ |
事實上Chrome是正確的,並且由此可發現microtasks並非在tasks的結束階段開始執行,而是在tasks中回調結束以後(只要沒有正在執行的JavaScript代碼)
tasks會順序執行,瀏覽器會在執行間隔從新渲染
microtasks會順序執行,執行時機爲
在沒有JavaScript代碼執行的callback以後在每個tasks以後
因爲我是前端初學者,對於JavaScript還很不熟悉,對事件循環的進程模型不是很瞭解,但願這篇文章可以幫助你們.
事件循環機制建議參考文章
阮一峯-->JavaScript 運行機制詳解:再談Event Looptasks建議參考文章
Jake Archibald-->Tasks, microtasks, queues and schedules