回调函数在调用堆栈中执行,即使它不为空

Callback function executing in the call stack even when it's not empty

当从鼠标触发点击事件时,它的行为符合预期:

首先,侦听器 1 被推入堆栈,在 Microtask 队列(或作业队列)中将 promise 1 排队。当侦听器 1 弹出时,堆栈变为空。并且承诺 1 回调在侦听器 2(正在任务队列(或回调队列)中等待)之前执行。承诺 1 回调弹出后,侦听器 2 被推入堆栈。所以输出为:

监听器 1 微任务 1 监听器 2 微任务 2

但是,当通过 JavaScript 代码触发点击时,它的行为会有所不同:

回调甚至在 click() 函数完成之前就被压入堆栈(即调用堆栈不为空)。这里的输出是:

监听器 1 监听器 2 微任务 1 微任务 2

代码如下:

window.onload = function(){
    document.getElementById("myBtn").addEventListener('click', () => {
        Promise.resolve().then(() => console.log('Microtask 1'));
        console.log('Listener 1');
    } );

    document.getElementById("myBtn").addEventListener('click', () => {
        Promise.resolve().then(() => console.log('Microtask 2'));
        console.log('Listener 2');
} );
}
function clickB(){

    document.getElementById("myBtn").click();
}
<!DOCTYPE html>
<html>
<button id="myBtn">Manual Click</button>
<button onclick="clickB()">JS Click</button>
</html>

我的理解是任务队列和微任务队列中的项目只有在调用堆栈为空时才会被推入调用堆栈。我可能做出了错误的假设。请随时纠正我。谢谢

只要<button onclick>是运行,.then()就不会被执行。

这段代码更好地展示了执行顺序的差异:

window.onload = function() {
  [document.body, myBtn].forEach(node => {
    node.addEventListener("click", function(e) {
      const {
        currentTarget,
        target
      } = e;
      new Promise((r) => {
        console.log(
          "Promise()",
          currentTarget.tagName
        )
        r();
      }).then(() => {
        console.log(
          ".then()",
          currentTarget.tagName
        )
      });
    });
  });
}
<button id="myBtn" onclick="console.log('hello world');">Manual Click</button>
<button onclick="myBtn.click(); console.log('I will happen before the then');">JS click</button>

您的观察是正确的,解释也很简单:
微任务队列只有在 JavaScript execution context stack (a.k.a. call-stack) is empty, (defined in cleanup after running a script, itself called by call a user's operation). Technically the event loop has quite a few calls to perform a microtask checkpoint 时才会被访问,但是 JS 调用栈非空的情况非常罕见,可以忽略不计。

通过eventTarget.dispatchEvent()调度事件是同步,没有新任务排队,回调只是从当前上下文调用,而且,JS调用栈不为空.

const target = new EventTarget();
target.addEventListener("foo", () => console.log("new foo event"));
console.log("before");
target.dispatchEvent(new Event("foo"));
console.log("after");

所以 微任务队列 也不会被访问,它只会在 JS 调用堆栈完成之后,在你的代码中就是原始点击事件的处理程序作业执行完毕。

然而,引擎“本地”调度的事件将为每个回调创建一个新的 JS 作业,因此在它们之间的每个回调中,JS 调用堆栈将为空,并且微任务队列将被访问。