该视频完美地解释了发生的事情,但似乎我们无论如何都必须解释它......
在“原生”事件的情况下,浏览器将“排队一个任务”以触发一个事件,该事件将被分派到您的目标并依次调用所有侦听器,最后调用它们的 JS 回调。由于每次回调执行之间 JS 堆栈都是空的,因此会执行一个微任务检查点,并且在这些回调期间解决的 Promise 会执行其回调。
只有在调用了所有这些侦听器之后,如果尚未引发事件的取消标志,则将执行默认操作。
const link = document.querySelector("a");
link.addEventListener("click", (evt) => {
console.log("event 1:", evt.defaultPrevented);
Promise.resolve().then( () => {
console.log("microtask 1:", evt.defaultPrevented); // false
evt.preventDefault();
});
});
link.addEventListener("click", (evt) => {
console.log("event 2:", evt.defaultPrevented); // true
Promise.resolve().then( () => {
console.log("microtask 2:", evt.defaultPrevented); // true
});
});
#target {
margin-top: 600vh;
}
<a href="#target">go to target</a>
<div id="target">target</div>
但是,对于由 JS 触发的合成事件,该事件是同步调度的,并且 JS 堆栈永远不会为空(因为它至少包含最初触发该事件的作业)。
因此,当浏览器在调用我们的每个 JS 回调后必须执行“运行脚本后清理”算法时,这一次它不会执行微任务检查点。
相反,它将一直持续到调度带有取消标志的事件算法的第 12 步仍然关闭,并将执行默认操作。只有在此之后,它才会从触发该合成事件的任何内容中返回,并且只有在浏览器能够执行微任务检查点之后,该脚本才会被清理。
在下面的代码片段中,我将使用一个<input type="checkbox">
元素,因为它的激活行为是同步<a>
的,而“导航链接”不是,因此不是一个很好的例子。
const input = document.querySelector("input");
input.addEventListener("click", (evt) => {
console.log("event fired");
Promise.resolve().then( () => {
console.log("microtask fired, preventing");
evt.preventDefault();
});
});
console.log("before click, is checkbox checked:", input.checked);
input.click();
console.log("after click, is checkbox checked:", input.checked);
#target {
margin-top: 600vh;
}
<input type="checkbox">
<div id="target">target</div>
所以现在我们只说了杰克在他的演讲中所说的话。
让我们更加关注 OP 的情况,他们希望仍然能够将其事件处理程序作为 Promise 处理,并希望能够防止事件的默认行为。
这是不可能的。
要让一个对象将 Promise 作为 Promise 使用,它的回调必须在 microtask-checkpoint 中执行。正如我们在上面看到的,微任务检查点只会在默认行为执行后才会执行。所以这是一个死胡同。
可以做什么:
现在,电话是你的。