当以编程方式单击按钮与单击DOM时,为什么任务/微任务执行顺序有所不同? [英] Why is there a difference in the task/microtask execution order when a button is programmatically clicked vs DOM clicked?

查看:25
本文介绍了当以编程方式单击按钮与单击DOM时,为什么任务/微任务执行顺序有所不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在DOM中单击按钮与以编程方式单击时,微任务/任务队列的执行顺序有所不同.

There's a difference in the execution order of the microtask/task queues when a button is clicked in the DOM, vs it being programatically clicked.

const btn = document.querySelector('#btn');

btn.addEventListener("click", function() {
  Promise.resolve().then(function() { console.log('resolved-1'); });
  console.log('click-1');
});

btn.addEventListener("click", function() {
  Promise.resolve().then(function() { console.log('resolved-2'); });
  console.log('click-2');
});

<button id='btn'>Click me !</button>

我的理解是,当调用栈为空时,事件循环将从微任务队列中获取回调以放置在调用栈中.当调用堆栈和微任务队列都为空时,事件循环开始从任务队列中进行回调.

My understanding is that when the callstack is empty, the event loop will take callbacks from the microtask queue to place on the callstack. When both the callstack and microtask queue are empty, the event loop starts taking callbacks from the task queue.

单击ID为 btn 的按钮时,两个"click"事件侦听器都将按声明的顺序放置在任务队列中.

When the button with the id btn is clicked, both "click" event listeners are placed on the task queue in order they are declared in.

// representing the callstack and task queues as arrays
callstack: []
microtask queue: []
task queue: ["click-1", "click-2"]

事件循环将"click-1"回调放置在调用堆栈上.它有一个立即解决的承诺,将"resolved-1"回调放置在微任务队列上.

The event loop places the "click-1" callback on the callstack. It has a promise that immediately resolves, placing the "resolved-1" callback on the microtask queue.

callstack: ["click-1"]
microtask queue: ["resolved-1"]
task queue: ["click-2"]

"click-1"回调执行其console.log,并完成.现在,微任务队列上有内容,因此事件循环采用"resolved-1"回调,并将其放置在调用堆栈中.

The "click-1" callback executes its console.log, and completes. Now there's something on the microtask queue, so the event loop takes the "resolved-1" callback and places it on the callstack.

callstack: ["resolved-1"]
microtask queue: []
task queue: ["click-2"]

"resolved-1"回调已执行.现在,调用堆栈和微任务队列都为空.

"resolved-1" callback is executed. Now both the callstack and microtask queue and are empty.

callstack: []
microtask queue: []
task queue: ["click-2"]

然后,事件循环再次看"任务队列,并重复该循环.

The event loop then "looks" at the task queue once again, and the cycle repeats.

// "click-2" is placed on the callstack
callstack: ["click-2"]
microtask queue: []
task queue: []

// Immediately resolved promise puts "resolved-2" in the microtask queue
callstack: ["click-2"]
microtask queue: ["resolved-2"]
task queue: []

// "click-2" completes ...
callstack: []
microtask queue: ["resolved-2"]
task queue: []

// "resolved-2" executes ...
callstack: ["resolved-2"]
microtask queue: []
task queue: []

// and completes
callstack: []
microtask queue: []
task queue: []

这将解释上面代码段中的输出

This would explain this output from the code snippet above

"hello click1"
"resolved click1"
"hello click2"
"resolved click2"

我希望它是相同的,然后以编程方式单击 btn.click().

I would expect it to be the same then I programatically click the button with btn.click().

const btn = document.querySelector('#btn');

btn.addEventListener("click", function() {
  Promise.resolve().then(function() { console.log('resolved-1'); });
  console.log('click-1');
});

btn.addEventListener("click", function() {
  Promise.resolve().then(function() { console.log('resolved-2'); });
  console.log('click-2');
});

btn.click()

<button id='btn'>Click me!</button>

但是,输出是不同的.

"hello click1"
"hello click2"
"resolved click1"
"resolved click2"

为什么以编程方式单击按钮时执行顺序有差异?

Why is there a difference in the execution order when button is programatically clicked ?

推荐答案

引人入胜的问题.

首先,最简单的部分:调用 click 时,这是 synchronous 调用,触发按钮上的所有事件处理程序.您可以看到,如果您在通话周围添加日志记录:

First, the easy part: When you call click, it's a synchronous call triggering all of the event handlers on the button. You can see that if you add logging around the call:

const btn = document.querySelector('#btn');

btn.addEventListener("click", function() {
  Promise.resolve().then(function() { console.log('resolved-1'); });
  console.log('click-1');
});

btn.addEventListener("click", function() {
  Promise.resolve().then(function() { console.log('resolved-2'); });
  console.log('click-2');
});


document.getElementById("btn-simulate").addEventListener("click", function() {
  console.log("About to call click");
  btn.click();
  console.log("Done calling click");
});

<input type="button" id="btn" value="Direct Click">
<input type="button" id="btn-simulate" value="Call click()">

由于处理程序是同步运行的,因此仅在两个处理程序都完成后才处理微任务.更快地处理它们将需要破坏JavaScript的运行完成语义.

Since the handlers are run synchronously, microtasks are processed only after both handlers have finished. Processing them sooner would require breaking JavaScript's run-to-completion semantics.

相反,当事件为已调度时通过DOM ,它更有趣:每个处理程序都已调用.调用处理程序包括在运行脚本后进行清理,其中包括执行微任务检查点,运行任何待处理的微任务.因此,由调用的处理程序安排的微任务将在下一个处理程序运行之前运行.

In contrast, when the event is dispatched via the DOM, it's more interesting: Each handler is invoked. Invoking a handler includes cleaning up after running script, which includes doing a microtask checkpoint, running any pending microtasks. So microtasks scheduled by the handler that was invoked get run before the next handler gets run.

这就是为什么它们在某种意义上是不同的:因为处理程序回调是在使用 click()时按顺序同步调用的,因此它们之间没有处理微任务的机会.

That's "why" they're different in one sense: Because the handler callbacks are called synchronously, in order, when you use click(), and so there's no opportunity to process microtasks between them.

稍微看一下为什么":为什么在使用 click()时会同时调用处理程序?主要是因为历史,这是早期浏览器所做的,因此无法更改.但是,如果您使用 dispatchEvent :

Looking at "why" slightly differently: Why are the handlers called synchronously when you use click()? Primarily because of history, that's what early browsers did and so it can't be changed. But they're also synchronous if you use dispatchEvent:

const e = new MouseEvent("click");
btn.dispatchEvent(e);

在那种情况下,处理程序仍将同步运行,因为使用该处理程序的代码可能需要查看 e ,以查看是否阻止了默认操作或类似的操作.(可以 进行不同的定义,以在事件完成时提供回调或类似的功能,但不是.我想认为不是出于简单性,与 click 的兼容性或两者兼而有之.)

In that case, the handlers are still run synchronously, because the code using it might need to look at e to see if the default action was prevented or similar. (It could have been defined differently, providing a callback or some such for when the event was done being dispatched, but it wasn't. I'd guess that it wasn't for either simplicity, compatibility with click, or both.)

这篇关于当以编程方式单击按钮与单击DOM时,为什么任务/微任务执行顺序有所不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆