在DOM中单击按钮时,与以编程方式单击时,微任务/任务队列的执行顺序有所不同。
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');
});Run Code Online (Sandbox Code Playgroud)
<button id='btn'>Click me !</button>Run Code Online (Sandbox Code Playgroud)
我的理解是,当调用栈为空时,事件循环将从微任务队列中获取回调以放置在调用栈中。当调用堆栈和微任务队列均为空时,事件循环开始从任务队列中进行回调。
单击带有id btn的按钮时,两个“ click”事件侦听器都将按声明它们的顺序放入任务队列中。
// representing the callstack and task queues as arrays
callstack: []
microtask queue: []
task queue: ["click-1", "click-2"]
Run Code Online (Sandbox Code Playgroud)
事件循环将其"click-1" callback放置在调用堆栈上。它有一个立即解决的承诺,将其"resolved-1" callback放置在微任务队列上。
callstack: ["click-1"]
microtask queue: ["resolved-1"]
task queue: ["click-2"]
Run Code Online (Sandbox Code Playgroud)
在"click-1" callback执行它的console.log,并完成。现在,微任务队列上有内容,因此事件循环接受"resolved-1" callback并将其放置在调用堆栈中。
callstack: ["resolved-1"]
microtask queue: []
task queue: ["click-2"]
Run Code Online (Sandbox Code Playgroud)
"resolved-1" callback被执行。现在,无论是调用堆栈还是微任务队列,都为空。
callstack: []
microtask queue: []
task queue: ["click-2"]
Run Code Online (Sandbox Code Playgroud)
然后,事件循环再次“看”任务队列,并重复该循环。
// "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: []
Run Code Online (Sandbox Code Playgroud)
这将解释上面代码片段的输出
"hello click1"
"resolved click1"
"hello click2"
"resolved click2"
Run Code Online (Sandbox Code Playgroud)
我希望它是相同的,然后以编程方式单击btn.click()。
// representing the callstack and task queues as arrays
callstack: []
microtask queue: []
task queue: ["click-1", "click-2"]
Run Code Online (Sandbox Code Playgroud)
callstack: ["click-1"]
microtask queue: ["resolved-1"]
task queue: ["click-2"]
Run Code Online (Sandbox Code Playgroud)
但是,输出是不同的。
"hello click1"
"hello click2"
"resolved click1"
"resolved click2"
Run Code Online (Sandbox Code Playgroud)
为什么以编程方式单击按钮时执行顺序有所不同?
T.J*_*der 10
有趣的问题。
首先,简单一点:调用时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');
});
document.getElementById("btn-simulate").addEventListener("click", function() {
console.log("About to call click");
btn.click();
console.log("Done calling click");
});Run Code Online (Sandbox Code Playgroud)
<input type="button" id="btn" value="Direct Click">
<input type="button" id="btn-simulate" value="Call click()">Run Code Online (Sandbox Code Playgroud)
由于处理程序是同步运行的,因此仅在两个处理程序都完成后才处理微任务。更快地处理它们将需要打破JavaScript的运行到完成语义。
相反,当事件通过DOM分派时,会更有趣:每个处理程序都会被调用。调用处理程序包括在运行脚本后进行清理,这包括执行微任务检查点,运行任何暂挂的微任务。因此,由调用的处理程序调度的微任务将在下一个处理程序运行之前运行。
这就是“为什么”它们在某种意义上不同的原因:因为处理程序回调是在使用时按顺序同步调用的click(),所以没有机会在它们之间处理微任务。
稍微看一下“为什么”:为什么在使用时同步调用处理程序click()?主要是因为历史,这是早期浏览器所做的,因此无法更改。但是,如果您使用它们,它们也是同步的dispatchEvent:
const e = new MouseEvent("click");
btn.dispatchEvent(e);
Run Code Online (Sandbox Code Playgroud)
在那种情况下,处理程序仍将同步运行,因为使用该处理程序的代码可能需要查看e以查看是否阻止了默认操作或类似的操作。(它可能已经被不同的定义,提供了一个回调或一些这样的时候做的事件被分派,但事实并非如此。我想,这是不是要么简便,兼容性click,或两者兼而有之。)
所以,Chrome 回答只是因为它很有趣(请参阅 TJ Crowder 对一般 DOM 回答的出色回答)。
btn.click();
Run Code Online (Sandbox Code Playgroud)
HTMLElement::click()在 C++ 中调用,它是 DOMElement 的对应物:
btn.click();
Run Code Online (Sandbox Code Playgroud)
它基本上围绕 dispatchMouseEvent 做了一些工作并处理边缘情况:
void HTMLElement::click() {
DispatchSimulatedClick(nullptr, kSendNoEvents,
SimulatedClickCreationScope::kFromScript);
}
Run Code Online (Sandbox Code Playgroud)
它在设计上是完全同步的,以使测试变得简单以及出于遗留原因(想想 DOMActivate 奇怪的事情)。
这只是一个直接调用,不涉及任务调度。EventTarget 通常是一个同步接口,它不会推迟事物,它早于 microtick 语义和承诺:]
| 归档时间: |
|
| 查看次数: |
336 次 |
| 最近记录: |