繁体   English   中英

使用以编程方式分派的事件防止默认操作

[英]Prevent default action with a programatically dispatched event

在 32:50 的演讲中,演讲者展示了以下代码:

const nextClick = new Promise(resolve => {
    link.addEventListener('click', resolve, { once: true });
});

nextClick.then(event => {
    event.preventDefault();
    // Handle event
});

他解释了为什么当点击事件直接由用户交互引起时,它会阻止click事件的默认操作,而不是在调度是程序化的情况下,例如link.click() 有没有一种简单的方法可以使它在后一种情况下与前一种情况相同?

编辑以详细说明动机

@Kaiido 在评论中询问为什么我(或者更确切地说是演讲者)在这里使用 Promise - 我假设他想建议将 function 作为事件侦听器直接附加到调用preventDefault 如果我想使用 Promise.all 或其他组合器将 promise 与其他承诺结合使用,则 promise 很方便。

该视频完美地解释了发生的事情,但似乎我们无论如何都必须解释它......

在“原生”事件的情况下,浏览器将“排队一个任务”以触发一个事件,该事件将被分派到您的目标并依次调用所有侦听器, 最后调用它们的 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 处理,并希望能够防止事件的默认行为。

这是不可能的。

为了让 object 与 Promises 一起作为 Promise 工作,它的回调必须在微任务检查点中执行。 正如我们在上面看到的,微任务检查点只会在默认行为执行才会执行。 所以这是一个死胡同。

可以做什么:

  • 阻止事件处理程序中的默认行为,而不是 Promise 反应中的默认行为:

     const link = document.querySelector("a"); const prom = new Promise( (resolve) => { link.addEventListener("click", (evt) => { evt.preventDefault(); resolve(evt); }, { once: true } ); }); prom.then( (evt) => console.log("clicked") ); // true link.click();
     #target { margin-top: 600vh; }
     <a href="#target">go to target</a> <div id="target">target</div>
    但这意味着您要么阻止所有默认行为,要么在偶数处理程序中移动一些逻辑,这可能会破坏首先在那里拥有 Promise 的想法。
    不过,如果您只是想在此事件之后链接某些东西,这可能是一个可行的解决方案。

  • 不要使用真正的 Promise,而只是使用类似 API 的东西,它将同步执行回调:

     class EventReaction { constructor( executor ) { this._callbacks = []; executor( this._resolver.bind( this ) ); } after( callback ) { if( this._done ) { callback(); } else { this._callbacks.push( callback ); } } _resolver( arg ) { this._callbacks.forEach( cb => cb( arg ) ); this._callbacks.length = 0; } } const link = document.querySelector("a"); const event_reaction = new EventReaction( (resolve) => { link.addEventListener("click", (evt) => { resolve(evt); }, { once: true } ); } ); event_reaction.after( (evt) => { console.log("preventing"); evt.preventDefault(); }); link.click();
     #target { margin-top: 600vh; }
     <a href="#target">go to target</a> <div id="target">target</div>
    但这不是 Promise,不能与 Promise 链接,也不能被 Promise 的任何方法使用。

现在,电话是你的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM