簡體   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