本文 AI 產出,尚未審核

JavaScript – DOM 與瀏覽器 API

單元:事件監聽(addEventListener)


簡介

在前端開發中,使用者與網頁的互動大多是透過事件(Event)來完成的。無論是點擊按鈕、鍵盤輸入、視窗捲動,或是觸控手勢,都會產生一個事件物件,並由程式碼加以處理。
addEventListener 是現代瀏覽器提供的 標準化事件註冊方式,它不僅支援多個同類型事件同時存在,還能控制事件的捕獲與冒泡階段,是取代舊式 onclickonload 等屬性寫法的首選。

掌握 addEventListener 的使用方法與最佳實踐,能讓你的程式碼 更具可維護性、可讀性,同時避免常見的錯誤與效能問題。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整了解這項必備技能。


核心概念

1. 什麼是事件?

  • 事件:使用者或瀏覽器在特定時刻所發生的行為,如 clickkeydownloadresize 等。
  • 事件物件(Event Object):瀏覽器在觸發事件時會自動產生,裡面包含事件類型、目標元素、座標、鍵值等資訊。
  • 事件流:事件在 DOM 樹中傳遞的過程,分為 捕獲階段(capture)目標階段(target)冒泡階段(bubble) 三個階段。

重要:了解事件流可以幫助你決定何時攔截或傳遞事件,避免不必要的副作用。

2. addEventListener 語法

target.addEventListener(type, listener, options);
參數 說明
target 需要監聽事件的 DOM 元素或 windowdocument 物件
type 事件類型字串(例如 'click''keydown'
listener 事件處理函式,接受一個 event 參數
options 可選的布林值或設定物件,常用屬性:
capture(是否在捕獲階段觸發)
once(執行一次後自動移除)
passive(指示不會呼叫 preventDefault()

:若省略 options,預設為 { capture: false, once: false, passive: false }

3. 常見事件類型

類別 代表事件 說明
滑鼠 clickdblclickmousedownmouseupmousemove 觸發於滑鼠操作
鍵盤 keydownkeyupkeypress 監聽鍵盤按鍵
表單 submitchangeinputfocusblur 針對表單元素的互動
視窗 loadresizescrollbeforeunload 與瀏覽器視窗相關
觸控 touchstarttouchmovetouchend 手機或平板的觸控事件

程式碼範例

以下提供 5 個實用範例,從基礎到稍微進階的使用情境,均附上說明註解。

範例 1️⃣ 基本點擊監聽

// 取得按鈕元素
const btn = document.querySelector('#myBtn');

// 註冊 click 事件
btn.addEventListener('click', function (e) {
  // e 為事件物件
  console.log('按鈕被點擊!');
  // 取得點擊的座標
  console.log(`點擊位置:(${e.clientX}, ${e.clientY})`);
});

重點:使用匿名函式作為 listener,e 讓你取得點擊的座標或其他屬性。


範例 2️⃣ 只執行一次的監聽(once

const modal = document.querySelector('#modalClose');

modal.addEventListener('click', function () {
  console.log('關閉彈窗');
  modal.parentElement.style.display = 'none';
}, { once: true });   // 點擊一次後自動解除監聽

技巧once: true 可避免手動呼叫 removeEventListener,適合一次性操作(如關閉提示訊息)。


範例 3️⃣ 使用 passive 提升捲動效能

// 監聽捲動事件,告訴瀏覽器此處不會呼叫 preventDefault()
window.addEventListener('scroll', function () {
  console.log('頁面捲動中...');
}, { passive: true });

說明passive: true 讓瀏覽器在捲動時不必等待 JavaScript 判斷是否會阻止預設行為,從而提升 滑順度


範例 4️⃣ 捕獲階段與冒泡階段的差異

<div id="outer" style="padding:20px;background:#f9c;">
  外層容器
  <button id="innerBtn">內層按鈕</button>
</div>
const outer = document.getElementById('outer');
const innerBtn = document.getElementById('innerBtn');

// outer 使用捕獲階段
outer.addEventListener('click', function () {
  console.log('外層捕獲階段觸發');
}, true);   // true 表示捕獲

// inner 使用冒泡階段(預設)
innerBtn.addEventListener('click', function (e) {
  console.log('內層冒泡階段觸發');
  // 停止事件向上冒泡
  e.stopPropagation();
});

觀察:先印出「外層捕獲階段觸發」再印出「內層冒泡階段觸發」;若在內層呼叫 stopPropagation(),外層的冒泡階段就不會再被觸發。


範例 5️⃣ 移除監聽與記憶函式(避免匿名函式無法移除)

function onResize(e) {
  console.log('視窗尺寸變更:', window.innerWidth, window.innerHeight);
}

// 加入監聽
window.addEventListener('resize', onResize);

// 需要時移除
document.getElementById('removeBtn').addEventListener('click', function () {
  window.removeEventListener('resize', onResize);
  console.log('已解除 resize 監聽');
});

要點必須使用具名函式(或將匿名函式存入變數)才能正確呼叫 removeEventListener


常見陷阱與最佳實踐

陷阱 說明 解決方案
使用匿名函式無法移除 removeEventListener 需要與 addEventListener 完全相同的函式參考。 使用具名函式或把匿名函式存入變數。
忘記 preventDefault() 某些表單或連結的預設行為會阻礙自訂行為。 在需要阻止預設時,於 listener 中呼叫 event.preventDefault()
過度綁定大量事件 大量元素分別綁定相同事件會造成記憶體與效能負擔。 使用 事件委派(Event Delegation)在父層一次監聽。
未考慮 passive 滾動或觸控事件若未標示 passive,會降低滑順度。 在捲動、觸控相關事件上加入 { passive: true }
捕獲與冒泡混用不當 不清楚事件流會導致意外的執行順序或無法阻止冒泡。 明確指定 capture 參數,必要時使用 stopPropagation()stopImmediatePropagation()

推薦的開發流程

  1. 先規劃事件流:決定哪裡需要捕獲,哪裡需要冒泡。
  2. 使用具名函式或變數保存 listener,方便日後移除。
  3. 盡量採用事件委派:例如在 <ul> 上監聽所有子項目的點擊。
  4. 根據需求設定 oncepassive,減少手動移除與提升效能。
  5. 測試不同裝置:觸控與鍵盤的事件行為可能不同,需確認兼容性。

實際應用場景

場景 需求 典型程式碼
動態表單驗證 在使用者輸入時即時檢查格式,並在送出前阻止錯誤提交。 input.addEventListener('input', validate); form.addEventListener('submit', e => { if (!isValid) e.preventDefault(); });
單頁應用(SPA)路由 監聽 hashchangepopstate 變化,更新畫面。 window.addEventListener('hashchange', renderPage);
拖曳介面 結合 mousedownmousemovemouseup 建立自訂拖曳行為。 element.addEventListener('mousedown', startDrag); document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', endDrag);
無限捲動 監聽 scroll,當捲動到頁底時自動載入更多資料。 window.addEventListener('scroll', checkBottom, { passive: true });
鍵盤快捷鍵 為特定功能設定 keydown 快捷鍵,提升使用者體驗。 document.addEventListener('keydown', e => { if (e.ctrlKey && e.key === 's') { e.preventDefault(); save(); } });

這些情境皆依賴 正確且高效的事件監聽,才能提供流暢且符合使用者期待的互動體驗。


總結

  • addEventListener現代 JavaScript 處理事件的核心 API,支援多種選項(captureoncepassive)與完整的事件流控制。
  • 透過 具名函式、事件委派、適當的選項設定,可以寫出 可維護、效能良好 的程式碼。
  • 常見的陷阱包括匿名函式無法移除、忽略 preventDefault()、過度綁定等,了解並避免這些問題是提升專案品質的關鍵。
  • 在實務開發中,從表單驗證、SPA 路由、拖曳介面到無限捲動、鍵盤快捷鍵,事件監聽無所不在,掌握它就等於掌握了與使用者互動的「入口」。

希望本篇文章能幫助你在日後的前端開發中,自信且正確 地運用 addEventListener,打造更佳的使用者體驗。祝開發順利! 🚀