事件處理(Event)
什麼是事件?
比喻:事件就像門鈴 🔔
有人按門鈴(事件發生)→ 你去開門(事件處理函數)。 使用者的每個動作(點擊、輸入、滾動)都是一個事件。
綁定事件
let btn = document.querySelector("#myBtn");
// 方式 1:addEventListener(推薦)
btn.addEventListener("click", function() {
console.log("按鈕被點擊了!");
});
// 方式 2:addEventListener + 箭頭函式
btn.addEventListener("click", () => {
console.log("按鈕被點擊了!");
});
// 方式 3:用具名函式(方便移除)
function handleClick() {
console.log("按鈕被點擊了!");
}
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick); // ← 移除事件
事件物件(Event Object)
btn.addEventListener("click", function(event) { // ← event 是事件物件
console.log(event.type); // ← "click"(事件類型)
console.log(event.target); // ← 被點擊的元素
console.log(event.clientX); // ← 滑鼠 X 座標
console.log(event.clientY); // ← 滑鼠 Y 座標
event.preventDefault(); // ← 阻止預設行為(如表單送出)
event.stopPropagation(); // ← 阻止事件冒泡
});
常見事件類型
滑鼠事件
el.addEventListener("click", handler); // 單擊
el.addEventListener("dblclick", handler); // 雙擊
el.addEventListener("mouseenter", handler); // 滑鼠移入
el.addEventListener("mouseleave", handler); // 滑鼠移出
el.addEventListener("mousemove", handler); // 滑鼠移動
鍵盤事件
document.addEventListener("keydown", (e) => {
console.log(e.key); // ← 按了什麼鍵("Enter"、"a"、"Escape")
console.log(e.code); // ← 鍵碼("KeyA"、"ArrowUp")
console.log(e.ctrlKey); // ← 是否按住 Ctrl
console.log(e.shiftKey); // ← 是否按住 Shift
});
表單事件
let input = document.querySelector("input");
let form = document.querySelector("form");
input.addEventListener("input", (e) => { // ← 每次輸入都觸發
console.log(e.target.value);
});
input.addEventListener("change", (e) => { // ← 失去焦點時觸發
console.log(e.target.value);
});
form.addEventListener("submit", (e) => {
e.preventDefault(); // ← 阻止表單送出(自己處理)
let formData = new FormData(form);
console.log(formData.get("username"));
});
事件委派(Event Delegation)
// ❌ 壞:對每個 li 都綁事件
document.querySelectorAll("li").forEach(li => {
li.addEventListener("click", () => { /* ... */ });
});
// 問題:如果動態新增 li,新的 li 不會有事件
// ✅ 好:在父元素上用事件委派
document.querySelector("ul").addEventListener("click", (e) => {
if (e.target.tagName === "LI") { // ← 檢查是不是 li 被點
console.log(e.target.textContent);
}
});
// 優點:動態新增的 li 也能觸發!
逐行解析:
ul.addEventListener("click", ...) -- 事件綁在父元素 ul 上
e.target -- 實際被點擊的元素(可能是 li、span 等)
e.target.tagName === "LI" -- 確認是 li 被點才處理
e.currentTarget -- 綁定事件的元素(ul)
防抖與節流
// 防抖(Debounce)— 停止操作後才執行
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 使用:搜尋框輸入 300ms 後才發送請求
let searchInput = document.querySelector("#search");
searchInput.addEventListener("input", debounce((e) => {
fetch(`/api/search?q=${e.target.value}`);
}, 300));
// 節流(Throttle)— 固定間隔執行一次
function throttle(fn, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 使用:滾動事件每 100ms 最多觸發一次
window.addEventListener("scroll", throttle(() => {
console.log("滾動中...");
}, 100));