【问题标题】:Prevent default action with a programatically dispatched event使用以编程方式分派的事件防止默认操作
【发布时间】:2021-03-14 05:58:33
【问题描述】:

在 32:50 的 this talk 中,演讲者显示以下代码:

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

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

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

编辑以详细说明动机

@Kaiido 在 cmets 中问我(或者更确切地说是演讲者)为什么在这里使用 Promise - 我假设他想建议直接将调用 preventDefault 的函数附加为事件侦听器。如果我想使用 Promise.all 或其他组合器将它与其他 Promise 组合起来,则 Promise 很方便。

【问题讨论】:

  • event.preventDefault(); 总是被调用,但如果以编程方式调度事件,它不会阻止默认操作。链接的视频解释了原因。
  • once 无关紧要。不同的是,当用户点击link时,click事件的默认动作被阻止,而link.click()被执行时则不然。
  • 上面的 cmets 是针对一个显然删除了他的 3 cmets 和他的答案以及我对此的评论的人。
  • 视频好像说明了一切,还有什么不明白的地方?为什么必须使用 Promise?
  • @Kaiido,如果我想使用 Promise.all 或其他东西将它与其他承诺结合起来,这个承诺很方便。视频只是陈述问题,解释行为,但没有提供解决方案。

标签: javascript promise dom-events event-loop web-scripting


【解决方案1】:

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

在“本机”事件的情况下,浏览器会将任务“排队”到fire an event,这会将dispatched 发送到您的目标,invoke 依次接收所有侦听器,finally call their JS callback .由于每次回调执行之间的 JS 堆栈都是空的,a microtask-checkpoint is performed,并且在这些回调期间得到解决的 Promise 将执行其回调。
只有在调用了所有这些侦听器之后,如果事件的 cancelled flag 尚未引发,则默认操作将被执行。

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 回调后必须执行“clean up after running script”算法时,它不会执行微任务检查点。
相反,它将一直持续到dispatch an event 算法的第 12 步,cancelled 标志 仍处于关闭状态,并将执行默认操作。只有在此之后,它才会从触发该合成事件的任何事情中返回,并且只有在浏览器能够执行微任务检查点之后,该脚本才会被清除。

在下面的 sn-p 中,我将使用 &lt;input type="checkbox"&gt; 元素,因为它的 activation behavior 是同步的,而 &lt;a&gt; 的“导航链接”不是,因此不是一个很好的例子.

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,它的回调必须在微任务检查点中执行。正如我们在上面看到的,微任务检查点只会在默认行为执行后执行。所以这是一条死胡同。

可以做什么:

  • 防止在事件处理程序中而不是在 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 的任何方法使用。

现在,该呼叫是您的了。

【讨论】:

    猜你喜欢
    • 2011-12-14
    • 1970-01-01
    • 2011-06-29
    • 2018-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多