博客 / 詳情

返回

手動點擊與代碼中調用click方法兩者的區別

問題描述

在看事件循環相關視頻的時候發現其中有一個例子不理解,查了資料才明白其中的緣由,遂以志之。問題是這樣的:

<button id="button">button</button>
button.addEventListener("click", () => {
    Promise.resolve().then(() => console.log("Microtask 1"));
    console.log("Listener 1");
});
button.addEventListener("click", () => {
    Promise.resolve().then(() => console.log("Microtask 2"));
    console.log("Listener 2");
});

要問的是:

  1. 你手動點擊button,打印的log順序是啥?
  2. 如果改成用js調用lick的方式,那麼輸出順序又是啥?

    // 在上面的js代碼最後一行新增
    button.click();

問題解析

首先,公佈一下答案。
第一種情況輸出是:

Listener 1
Microtask 1
Listener 2
Microtask 2

第二種情況的輸出是:

Listener 1
Listener 2
Microtask 1
Microtask 2

先回顧一下時間循環中的部分知識點:
在事件循環中,點擊事件的回調屬於Macrotask,Promise.then的回調屬於Microtask,先執行一個Macrotask,然後等調用棧為空之後就會執行所有Microtask。

結合上述知識,情況1的答案是完全符合這個説法的。
但是情況2就不能理解了,為何連着執行了兩個Macrotask,然後才執行的Microtask,難道這個這個邏輯有特殊情況?非也!
問題的核心在於手動點擊的方式回調是異步的,而button.click()這種調用方式它是同步的,沒錯,它是同步調用的。

和經由瀏覽器觸發,並通過事件循環異步調用事件處理程序的“原生”事件不同,dispatchEvent() 會同步調用事件處理函數。在 dispatchEvent() 返回之前,所有監聽該事件的事件處理程序將在代碼繼續前執行並返回。
來源文檔MDN:EventTarget.dispatchEvent()

明白這一點之後再看情況2就很好理解了,就相當於:

function fn1() {
    Promise.resolve().then(() => console.log("Microtask 1"));
    console.log("Listener 1");
});
function fn2 {
    Promise.resolve().then(() => console.log("Microtask 2"));
    console.log("Listener 2");
});

// button.click();
fn1();
fn2();

最後

這個題目出現在一個與事件循環相關的視頻中,於是有不少文章在解釋情況2的時候都會解釋説此時調用棧不為空,所以不能執行Microtask,但卻沒有解釋為啥不為空,以及調用棧裏有啥。我看了解釋完全摸不着頭腦,最後在瞭解了dispatchEvent同步調用的知識點後才恍然大悟,這根本就是兩碼事嘛!吃了沒文化的虧。

user avatar 1023 頭像 peter-wilson 頭像 jidongdehai_co4lxh 頭像 ivyzhang 頭像 huishou 頭像 ziyeliufeng 頭像 dujing_5b7edb9db0b1c 頭像 codepencil 頭像 shaochuancs 頭像 sunhengzhe 頭像 pugongyingxiangyanghua 頭像 yilezhiming 頭像
72 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.