事件循環
三個概念
調用棧,先進后出
宏任務隊列(存放宏任務的,隊列先進先出)
微任務隊列
異步任務又分為宏任務和微任務
宏任務,macrotask,也叫tasks
setTimeout
setInterval
setImmediate (Node獨有)
requestAnimationFrame (瀏覽器獨有)
I/O
DOM/Web events (onclick, onkeydown, XMLHttpRequest etc)
UI rendering (瀏覽器獨有)
微任務,microtask,也叫jobs
process.nextTick (Node獨有)
Promise
Object.observe
MutationObserver
事件循環執行的過程
一輪事件循環只取一個宏任務,宏任務中的同步代碼執行完后,就依次從前往后執行微任務隊列中的微任務
代碼執行初期,調用棧為空,宏任務隊列中只有script這個宏任務,微任務隊列為空
將script這個宏任務放入調用棧中,執行同步代碼
遇到微任務,就將微任務加到微任務隊列中,遇到宏任務,就將宏任務加到宏任務隊列中,遇到函數調用的,就將該函數加到調用棧中,執行該函數中的同步代碼,遇到微任務或者是宏任務,就將它們加到對應的任務隊列中
等調用棧中的同步代碼執行完成后,查看微任務隊列中是否有微任務,有則從微任務隊列頭部開始逐一執行微任務中的同步代碼,如果該微任務中有宏任務,則將宏任務加到宏任務隊列的末尾,如果微任務中微任務,則將微任務加到微任務隊列尾部中,並在此次事件循環中執行完
微任務隊列中的任務都執行完后,繼續下一輪事件循環,從宏任務頭部取出第一個任務,執行同步代碼,之后重復3和4步
下面見一個例子:
function a () { console.log(1) let macrotask1 = setTimeout(() =>{ console.log(2) let microtask4 = new Promise((resolve,reject) =>{ console.log(11) resolve() }).then(res =>{ console.log(12) }) console.log(13) }, 0) let microtask1 = new Promise((resolve,reject) =>{ console.log(3) resolve() }).then(res =>{ console.log(4) let microtask3 = new Promise((resolve,reject) =>{ console.log(14) resolve() }).then(res =>{ console.log(15) }) console.log(16) }) b() console.log(9) } function b (){ console.log(5) let macrotask2 = setTimeout(() =>{ console.log(6) }, 0) let microtask2 = new Promise((resolve,reject) =>{ console.log(7) resolve() }).then(res =>{ console.log(8) }) } a() console.log(10) // 1 3 5 7 9 10 4 14 16 8 15 2 11 13 12 6 //
為了更好說明,microtask表示代碼中微任務,macrotask表示宏任務
將script宏任務放入調用棧,執行同步代碼,所以先打印1
接着遇到macrotask1(setTimeout,因為是0秒,所以立馬加入到宏任務隊列中)第一個宏任務,將其加入宏任務隊列的尾部
接着遇到microtask1(promise)第一個微任務,new Promise在實例的過程中執行代碼都是同步進行的,只有回調then()才是微任務,所以打印3,並將then加到了微任務隊列中
接着遇到調用b函數,所以先打印了5
接着遇到macrotask2(setTimeout)第二個宏任務,將其加到紅任務隊列的尾部
接着遇到microtask2(promise)第二個微任務,實例化執行同步代碼,打印7,並將then中打印8的代碼加到微任務隊列的尾部
至此b函數同步代碼執行完成,接着執行a函數中最后的同步代碼,打印9
至此a函數同步代碼執行完成,
接着執行script最后的同步代碼,打印10
至此第一輪事件循環的同步代碼執行完成
此時微任務隊列中有microtask1和microtask2兩個微任務,微任務隊列依次從前往后執行微任務
執行第一個microtask1微任務,打印4
接着又遇到了第三個微任務,執行實例化同步代碼,打印14,並將then中打印15的代碼加到微任務隊列的尾部,此時微任務隊列中有三個微任務
接着執行同步代碼console.log(16),打印16,至此第一個微任務執行完成,從微任務隊列中刪除
接着執行第二個microtask2微任務,打印8,至此第二個微任務執行完成,從微任務隊列中刪除
接着執行第三個microtask3微任務,打印15,至此第二個微任務執行完成,從微任務隊列中刪除
此時微任務隊列已經為空,第一輪事件循環執行完成,並從宏任務隊列中刪除
接着執行下一輪事件循環,取出第一個宏任務,並執行同步代碼,打印2
接着遇到microtask4(promise)第四個微任務,實例化執行同步代碼,打印11,並將then中打印12的代碼加到微任務隊列中,此時微任務隊列只有這個微任務
接着執行macrotask1宏任務中最后的同步代碼,打印13
接着查看微任務隊列,發現有一個微任務microtask4
接着執行微任務microtask4,打印12,至此該微任務執行完成,並從微任務隊列中刪除,此時微任務隊列為空,至此第二輪事件循環執行完成,並從宏任務隊列中刪除
接着執行下一輪事件循環,從宏任務隊列取出第一個宏任務,也就是macrotask2,執行該宏任務中同步代碼,打印6
佛山vi設計https://www.houdianzi.com/fsvi/ 豌豆資源搜索大全https://55wd.com
dom事件回調函數是宏任務,那么見下代碼:
console.log(1); new Promise((resolve, reject) => { resolve(3) }).then(() => { console.log(2); }) var button = document.querySelector(".button"); button.addEventListener('click', () => { console.log(3); }) button.click() // 1 3 2
按照上面說的執行規則,按理說打印順序是 123,但運行的結果是1 3 2,那么問題出在哪里呢,那是因為人工合成(synthetic)的事件派發(dispatch)是同步執行的,包括執行click()和dispatchEvent()這兩種方式。直接 domEle.click() 和 真的在頁面上點擊然后觸發事件回調應該是不一樣的。所以上面的代碼執行順序就像下面的:
console.log(1); new Promise((resolve, reject) => { resolve(3) }).then(() => { console.log(2); }) console.log(3)
vue.nextTick的原理
vue nextTick其實就是將dom更新后的操作當成微任務加到dom更新微任務的后面,保證其執行的順序,再不行就使用setTimeout宏任務代替,在下一輪事件循環中執行,這也是為什么Promise,MutationObserver的優先級比setTimeout高