JavaScript – DOM 與瀏覽器 API
單元:事件監聽(addEventListener)
簡介
在前端開發中,使用者與網頁的互動大多是透過事件(Event)來完成的。無論是點擊按鈕、鍵盤輸入、視窗捲動,或是觸控手勢,都會產生一個事件物件,並由程式碼加以處理。addEventListener 是現代瀏覽器提供的 標準化事件註冊方式,它不僅支援多個同類型事件同時存在,還能控制事件的捕獲與冒泡階段,是取代舊式 onclick、onload 等屬性寫法的首選。
掌握 addEventListener 的使用方法與最佳實踐,能讓你的程式碼 更具可維護性、可讀性,同時避免常見的錯誤與效能問題。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整了解這項必備技能。
核心概念
1. 什麼是事件?
- 事件:使用者或瀏覽器在特定時刻所發生的行為,如
click、keydown、load、resize等。 - 事件物件(Event Object):瀏覽器在觸發事件時會自動產生,裡面包含事件類型、目標元素、座標、鍵值等資訊。
- 事件流:事件在 DOM 樹中傳遞的過程,分為 捕獲階段(capture)、目標階段(target)、冒泡階段(bubble) 三個階段。
重要:了解事件流可以幫助你決定何時攔截或傳遞事件,避免不必要的副作用。
2. addEventListener 語法
target.addEventListener(type, listener, options);
| 參數 | 說明 |
|---|---|
target |
需要監聽事件的 DOM 元素或 window、document 物件 |
type |
事件類型字串(例如 'click'、'keydown') |
listener |
事件處理函式,接受一個 event 參數 |
options |
可選的布林值或設定物件,常用屬性: • capture(是否在捕獲階段觸發)• once(執行一次後自動移除)• passive(指示不會呼叫 preventDefault()) |
註:若省略
options,預設為{ capture: false, once: false, passive: false }。
3. 常見事件類型
| 類別 | 代表事件 | 說明 |
|---|---|---|
| 滑鼠 | click、dblclick、mousedown、mouseup、mousemove |
觸發於滑鼠操作 |
| 鍵盤 | keydown、keyup、keypress |
監聽鍵盤按鍵 |
| 表單 | submit、change、input、focus、blur |
針對表單元素的互動 |
| 視窗 | load、resize、scroll、beforeunload |
與瀏覽器視窗相關 |
| 觸控 | touchstart、touchmove、touchend |
手機或平板的觸控事件 |
程式碼範例
以下提供 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()。 |
推薦的開發流程
- 先規劃事件流:決定哪裡需要捕獲,哪裡需要冒泡。
- 使用具名函式或變數保存 listener,方便日後移除。
- 盡量採用事件委派:例如在
<ul>上監聽所有子項目的點擊。 - 根據需求設定
once、passive,減少手動移除與提升效能。 - 測試不同裝置:觸控與鍵盤的事件行為可能不同,需確認兼容性。
實際應用場景
| 場景 | 需求 | 典型程式碼 |
|---|---|---|
| 動態表單驗證 | 在使用者輸入時即時檢查格式,並在送出前阻止錯誤提交。 | input.addEventListener('input', validate); form.addEventListener('submit', e => { if (!isValid) e.preventDefault(); }); |
| 單頁應用(SPA)路由 | 監聽 hashchange 或 popstate 變化,更新畫面。 |
window.addEventListener('hashchange', renderPage); |
| 拖曳介面 | 結合 mousedown、mousemove、mouseup 建立自訂拖曳行為。 |
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,支援多種選項(capture、once、passive)與完整的事件流控制。- 透過 具名函式、事件委派、適當的選項設定,可以寫出 可維護、效能良好 的程式碼。
- 常見的陷阱包括匿名函式無法移除、忽略
preventDefault()、過度綁定等,了解並避免這些問題是提升專案品質的關鍵。 - 在實務開發中,從表單驗證、SPA 路由、拖曳介面到無限捲動、鍵盤快捷鍵,事件監聽無所不在,掌握它就等於掌握了與使用者互動的「入口」。
希望本篇文章能幫助你在日後的前端開發中,自信且正確 地運用 addEventListener,打造更佳的使用者體驗。祝開發順利! 🚀