【開發筆記】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();
});