前幾天在B站看到個視頻,講解了事件循環,裏面的有一道事件循環的題目,估計很多人不怎麼細心想的話會做錯。
題目內容:有如下代碼塊,請寫出點擊了 button 後,控制檯的輸出內容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">click</button>
<script>
var oBtn = document.getElementById('btn')
oBtn.addEventListener('click', function () {
Promise.resolve().then(() => console.log('微任務 1'))
console.log('listener1')
})
oBtn.addEventListener('click', function () {
Promise.resolve().then(() => console.log('微任務 2'))
console.log('listener2')
})
</script>
</body>
</html>
答案:
我個人的理解,先輸出 listener1 和 微任務1,後再輸出 listener2 和 微任務2,是因爲js是單線程執行的,所以事件肯定也是從第一個 EventListener 開始執行的。那麼在執行第一個的時候,由於 promise 是走微任務的,所以先輸出 listener1,接下來肯定有挺多人會認爲是要執行 Listener2 中的代碼了,但其實不是,在 javascript 的堆棧中,監聽 click 的事件結束了,在目前的微任務隊列中,裏面只有一個 Listener1 的 Promise,由於第2個 EventListener 還暫時沒執行到,所以就會輪到 Promise 的這個微任務了。以此類推,EventListener2 中的打印也是如此。
但是如果題目改成如下,控制檯將輸出什麼呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">click</button>
<script>
var oBtn = document.getElementById('btn')
oBtn.addEventListener('click', function () {
Promise.resolve().then(() => console.log('微任務 1'))
console.log('listener1')
})
oBtn.addEventListener('click', function () {
Promise.resolve().then(() => console.log('微任務 2'))
console.log('listener2')
})
oBtn.click()
</script>
</body>
</html>
答案:
那這個輸出怎麼是 listener1 和 listener2 先輸出後再輸出 微任務1 和 微任務2 啊?在 javascript 的堆棧中,click() 運行,然後依次執行 listener1 和 listener2 的事件,也就是先打印 listener1,將 微任務1 推入微任務隊列中,然後打印 listener2,將 微任務2 推入微任務隊列中,此時微任務隊列中,有兩個微任務,並且 click 事件執行完畢,javascript 堆棧中的 click() 已經執行結束,接下來就是執行按順序執行微任務隊列中的了,故有以上輸出。針對題目二,我手工畫了個簡單的草圖,大概表示一下過程: