哈嘍!大家好!我是木瓜太香,我又來嘞,今天來說說前端面試中經常別問到的 JS 事件環問題。
JS 事件環
JS 程式的執行是離不開事件環機制的,這個機制保證在發生某些事情的時候我們有機會執行一個我們事先預定好的函式,事情發生的時候 JS 會將相應的函式入棧執行然後出棧,但是關於事件環我們還有一些未知的東西,例如,setTimeout 我們習慣稱他為定時器,但是可能很多人沒有意識到,這個東西和我們常用的一些事件沒什麼不同,只不過我們通常所說的事件大多需要使用者觸發,而 setTimeout 不用使用者自己觸發,而是指定時間之後觸發;那麼問題來了,如果我們將時間設定為 0 會發生什麼?會立即執行麼?
setTimeout、DOM或者 HTTP請求這部分其實並不在 v8 引擎中,這些屬於 web API,javascript 是一個單執行緒的語言,也就意味著一次只能做一件事情,這個事實從未改變
執行原理
JS 中所有的方法都會被推入棧中執行,執行完成被彈出,在遇到非同步程式碼的時候,例如 setTimeout MutationObserver Promise 非同步的部分會由其他掌管 webAPI 的地方執行,等非同步有結果之後,回撥函式會進入相應的佇列,Promise MutationObserver 回撥進入微任務佇列,setTimeout setInterval requestAnimationFrame 進入巨集任務佇列。等待主執行緒的執行棧空了,微任務佇列立刻被推入棧中執行,執行完畢開始執行巨集任務佇列
一個經典的例子
html
<div class="outer">
<div class="inner"></div>
</div>
js
// 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);
以上程式碼在,手動點選 inner
元素的時候會有如下輸出
click
promise
mutate
click
promise
mutate
timeout
timeout
截止 2020年8月份 chrome edge opera firefox 的結果是統一的,但是在此之前的版本可能會有不同的輸出。
一個奇怪的現象
上述程式碼我們不使用手動觸發點選,而是使用 inner.click()
觸發點選,其結果會有很大的不同
click
click
promise
mutate
promise
timeout
timeout
造成以上巨大差異的原因是,手動點選,不是通過函式進入執行棧的方式觸發點選事件的回撥,所以inner 的回撥執行完了主執行緒中的執行棧就是空的可以直接執行佇列中任務,然後事件冒泡導致的回撥函式才被推入棧執行;而 click 方法的點選則是通過將 click 推入棧中執行來達到的,inner 的點選回撥執行完了之後 click 方法並沒有被彈出棧,而是直接執行冒泡的下一個回撥,由於下一個回撥有一個重複的 屬性設定 這是不會重複觸發 MutationObserver的所以 mutate 的輸出只會有一個。等所有的冒泡回撥被執行完畢 click 函式才會被彈出棧。
最後注意,瀏覽器會盡量預先執行較為敏感的操作。
有前端問題需要討論的可以加我的qun:237871108。也可以通過嗶哩嗶哩搜尋木瓜太香找到我。