首頁
文章
隱私
  • 繁體中文
  • 简体中文
首頁
文章
隱私
  • 繁體中文
  • 简体中文

【開發筆記】JavaScript EventListener 正確的使用姿勢

addEventListener(type, listener) 雖然是簡單的 Web API 調用,但是如果沒有正確地去意識調用所填入的功能形式,往往會因為記憶體參照的目標不同,有可能導致事件監聽無法移除,進而持續地消耗 runtime 資源,甚至產生奇怪的運行結果

留意事件監聽回調方法的配置

最常犯的錯誤,就是忽略了 anonymous function 其實是各自獨立的記憶體參照。舉例來說,如果我們在迴圈內調用 addEventListener(type, listener) ,並且填入 anonymous function 作為 listener。如此一來,每當迴圈執行 addEventListener(type, listener) 時,就會持續地創建新的記憶體參照給 anonymous function(參考代碼如下)。

const element = document.getElementsByTagName("aTagName");
let i = 0;
while (i < 10) {
  i++;
  element.addEventListener("keypress", 
      (event) => {
        if (event.key === "Enter") {
          event.preventDefault();
          // do something here...
        }
      }, 
      false)
}

至於解決的方法,只要在迴圈外先定義宣告好方法就可以了(參考代碼如下)。

function processEvent(event) {
    if (event.key === "Enter") {
      event.preventDefault();
      // do something here...
    }
}

const element = document.getElementsByTagName("aTagName");
let i = 0;
while (i < 10) {
  i++;
  element.addEventListener("keypress",  processEvent, false);
}

留意事件監聽移除的配置必須與新增的配置相同

由於事件監聽移除的配置必須與新增的配置相同,所以作為 Listener 的方法需要一個變量參照(參考代碼如下)。

const listener = function processEvent(event) {
    if (event.key === "Enter") {
      event.preventDefault();
      // do something here...
    }
}

element.addEventListener("keypress",  processEvent, false);
// 事件監聽移除的配置必須與新增的配置相同
element.removeEventListener("keypress",  processEvent, false);

Capture Phase 和 Bubbling Phase

Capture Phase 和 Bubbling Phase 是兩種相反的事件傳播順序。Capture Phase 是從 Window 開始,依序 Document -> <html> -> <body> -> .... -> 監聽的元素。反之,Bubbling Phase 是從監聽的元素開始,由內向外傳播 ... -> <body> -> <html> -> Document -> Window。

默認的情況是採用 Bubbling Phase,也就是 addEventListener("keypress", processEvent) 或 addEventListener("keypress", processEvent, false)。採用 Capture Phase 監聽則需要配置成 addEventListener("keypress", processEvent, false) 或 addEventListener("keypress", processEvent, { useCapture: true })。

同場加映 passive 和 once

addEventListener(type, listener, option) 在 option 處除了可以配置 useCapture,還有 passive 和 once 可以設定控制。passive 會影響畫面渲染時會不會被 listener 給阻斷。默認狀態下 { passive: true },也就是畫面渲染時不會被 listener 給阻斷。

而 once 比較容易理解,顧名思義,當設置 { once: true} 時,可以強制讓該事件只會觸發監聽方法一次。

事件屬性中的 target 和 currentTarget

最後就是要小心事件屬性的 target 和 currentTarget 有可能不同。current target 表示的是 addEventListener 綁定的元素,而 target 則是引發該事件的元素。舉例來說,當使用者點擊頁面上某個連結時,target 指的是監聽事件的元素 <a>,而 currentTarget 則是 addEventListener 所綁定的元素(參考代碼如下)。

const anchor = document.querySelectorAll( 'a' )[0];
const div = document.querySelectorAll( 'div' )[0];

div.addEventListener('keypress', function(event) {
    console.log(event.target, event.currentTarget );
    // event.target 是 anchor,event.currentTarget 是 div
    event.preventDefault();
});