jQuery 事件處理 (Events)
jQuery 提供了統一且簡潔的方式來處理各種 DOM 事件,包括滑鼠事件、鍵盤事件、表單事件等,並且自動處理跨瀏覽器的相容性問題。
事件綁定
on() - 標準綁定方式
on() 是 jQuery 推薦的事件綁定方法:
// 基本語法
$(selector).on(events, handler);
// 綁定 click 事件
$('#btn').on('click', function () {
alert('按鈕被點擊了!');
});
// 綁定多個事件(空格分隔)
$('input').on('focus blur', function () {
$(this).toggleClass('focused');
});
// 使用物件綁定多個事件
$('#box').on({
mouseenter: function () {
$(this).addClass('hover');
},
mouseleave: function () {
$(this).removeClass('hover');
},
click: function () {
$(this).toggleClass('active');
},
});
傳遞資料給事件處理函式
$('#btn').on('click', { name: 'John', id: 123 }, function (event) {
console.log(event.data.name); // 'John'
console.log(event.data.id); // 123
});
off() - 解除綁定
// 移除特定事件的所有處理函式
$('#btn').off('click');
// 移除所有事件
$('#btn').off();
// 移除特定處理函式(需要使用命名函式)
function handleClick() {
console.log('clicked');
}
$('#btn').on('click', handleClick);
$('#btn').off('click', handleClick);
命名空間
使用命名空間可以更精確地管理事件:
// 綁定帶命名空間的事件
$('#btn').on('click.myPlugin', function () {
console.log('plugin click');
});
$('#btn').on('click.analytics', function () {
console.log('analytics click');
});
// 只移除特定命名空間的事件
$('#btn').off('click.myPlugin');
// 移除某命名空間下的所有事件
$('#btn').off('.myPlugin');
舊方法提醒:
bind()、unbind()、delegate()、undelegate() 在 jQuery 3.0 已被棄用,請統一使用 on() 和 off()。事件委派 (Event Delegation)
事件委派是將事件綁定在父元素上,利用事件冒泡來處理子元素的事件。這對於動態新增的元素特別有用。
// 語法
$(parentSelector).on(events, childSelector, handler);
// 範例:綁定在 ul 上,但處理 li 的點擊
$('ul').on('click', 'li', function () {
$(this).toggleClass('selected');
});
// 動態新增的 li 也會有效
$('ul').append('<li>新項目</li>');
事件委派的好處
- 動態元素:新增的元素自動具有事件處理
- 效能:只需要一個事件處理函式,而非每個元素各一個
- 記憶體:減少事件處理函式的數量
// 不好的做法 - 每個按鈕都綁定事件
$('.delete-btn').on('click', function () {
$(this).closest('tr').remove();
});
// 問題:新增的按鈕不會有事件
// 好的做法 - 使用事件委派
$('table').on('click', '.delete-btn', function () {
$(this).closest('tr').remove();
});
// 新增的按鈕也會有效
常用事件
滑鼠事件
$('#box').on('click', fn); // 點擊
$('#box').on('dblclick', fn); // 雙擊
$('#box').on('mouseenter', fn); // 滑鼠進入(不冒泡)
$('#box').on('mouseleave', fn); // 滑鼠離開(不冒泡)
$('#box').on('mouseover', fn); // 滑鼠移入(冒泡)
$('#box').on('mouseout', fn); // 滑鼠移出(冒泡)
$('#box').on('mousemove', fn); // 滑鼠移動
$('#box').on('mousedown', fn); // 滑鼠按下
$('#box').on('mouseup', fn); // 滑鼠放開
$('#box').on('contextmenu', fn); // 右鍵選單
hover() - 滑鼠懸停
hover() 是 mouseenter 和 mouseleave 的簡寫:
// 兩個函式:進入和離開
$('#box').hover(
function () {
$(this).addClass('hover');
},
function () {
$(this).removeClass('hover');
}
);
// 一個函式:進入和離開執行相同動作
$('#box').hover(function () {
$(this).toggleClass('hover');
});
鍵盤事件
$(document).on('keydown', fn); // 按鍵按下
$(document).on('keyup', fn); // 按鍵放開
$(document).on('keypress', fn); // 按鍵按下(已棄用,建議用 keydown)
// 取得按下的鍵
$(document).on('keydown', function (e) {
console.log('鍵碼:', e.which); // 鍵碼
console.log('按鍵:', e.key); // 按鍵名稱
console.log('Ctrl:', e.ctrlKey); // 是否按住 Ctrl
console.log('Shift:', e.shiftKey); // 是否按住 Shift
console.log('Alt:', e.altKey); // 是否按住 Alt
// 檢查特定按鍵
if (e.key === 'Enter') {
// 按下 Enter
}
if (e.key === 'Escape') {
// 按下 Esc
}
// 組合鍵
if (e.ctrlKey && e.key === 's') {
e.preventDefault(); // 阻止瀏覽器儲存
// 自訂儲存動作
}
});
表單事件
$('input').on('focus', fn); // 獲得焦點
$('input').on('blur', fn); // 失去焦點
$('input').on('change', fn); // 值改變(失去焦點時)
$('input').on('input', fn); // 值改變(即時)
$('input').on('select', fn); // 文字被選取
$('form').on('submit', fn); // 表單送出
$('form').on('reset', fn); // 表單重設
// change vs input
$('input').on('change', function () {
// 失去焦點時才觸發
});
$('input').on('input', function () {
// 每次輸入都觸發(即時驗證常用)
});
文件/視窗事件
// 文件就緒
$(document).ready(fn);
// 或
$(fn);
// 頁面完全載入(含圖片等資源)
$(window).on('load', fn);
// 視窗大小改變
$(window).on('resize', fn);
// 捲動
$(window).on('scroll', fn);
// 離開頁面前
$(window).on('beforeunload', function () {
return '確定要離開嗎?';
});
事件物件 (Event Object)
事件處理函式會接收一個事件物件,包含事件的相關資訊:
$('#box').on('click', function (event) {
// 事件類型
console.log(event.type); // 'click'
// 目標元素(實際被點擊的元素)
console.log(event.target); // DOM 元素
// 綁定事件的元素(等同於 this)
console.log(event.currentTarget); // DOM 元素
// 滑鼠座標
console.log(event.pageX); // 相對於文件
console.log(event.pageY);
console.log(event.clientX); // 相對於視窗
console.log(event.clientY);
// 事件發生時間
console.log(event.timeStamp);
// 傳遞的資料
console.log(event.data);
// 相關元素(mouseover/mouseout)
console.log(event.relatedTarget);
});
阻止預設行為
$('a').on('click', function (event) {
event.preventDefault(); // 阻止連結跳轉
// 自訂處理
});
$('form').on('submit', function (event) {
event.preventDefault(); // 阻止表單送出
// 使用 Ajax 送出
});
阻止事件冒泡
$('.child').on('click', function (event) {
event.stopPropagation(); // 阻止事件冒泡到父元素
});
// 同時阻止預設行為和冒泡
$('a').on('click', function (event) {
event.preventDefault();
event.stopPropagation();
// 或者
return false; // 等同於上面兩行
});
stopImmediatePropagation()
阻止同一元素上其他處理函式執行,並阻止冒泡:
$('#btn').on('click', function (event) {
console.log('第一個處理函式');
event.stopImmediatePropagation();
});
$('#btn').on('click', function () {
console.log('第二個處理函式'); // 不會執行
});
觸發事件
trigger() - 觸發事件
// 觸發 click 事件
$('#btn').trigger('click');
// 簡寫
$('#btn').click();
// 觸發並傳遞資料
$('#btn').trigger('click', ['參數1', '參數2']);
// 接收資料
$('#btn').on('click', function (event, param1, param2) {
console.log(param1, param2);
});
// 觸發自訂事件
$('#box').trigger('myCustomEvent');
triggerHandler() - 只觸發處理函式
與 trigger() 不同,triggerHandler():
- 不會觸發事件的預設行為
- 不會冒泡
- 只影響第一個匹配的元素
- 返回處理函式的返回值
// trigger() 會觸發 focus 的預設行為(獲得焦點)
$('input').trigger('focus');
// triggerHandler() 只執行處理函式
$('input').triggerHandler('focus');
一次性事件
one() - 只執行一次
// 事件只會觸發一次
$('#btn').one('click', function () {
console.log('只會執行一次');
});
// 多個事件各執行一次
$('#btn').one('click mouseenter', function (e) {
console.log(e.type + ' 觸發了');
});
// 搭配事件委派
$('ul').one('click', 'li', function () {
console.log('第一次點擊 li');
});
自訂事件
// 定義自訂事件
$('#box').on('highlight', function (event, color) {
$(this).css('background-color', color || 'yellow');
});
// 觸發自訂事件
$('#btn').on('click', function () {
$('#box').trigger('highlight', ['red']);
});
// 實際應用:發布/訂閱模式
var $events = $({}); // 建立事件中心
// 訂閱
$events.on('userLoggedIn', function (e, user) {
console.log('使用者登入:', user.name);
});
// 發布
$events.trigger('userLoggedIn', [{ name: 'John' }]);
實用範例
表單驗證
$('form').on('submit', function (e) {
e.preventDefault();
var isValid = true;
$(this)
.find('input[required]')
.each(function () {
if (!$(this).val().trim()) {
$(this).addClass('error');
isValid = false;
} else {
$(this).removeClass('error');
}
});
if (isValid) {
this.submit(); // 原生 submit
}
});
// 即時驗證
$('input').on('input', function () {
var $input = $(this);
if ($input.val().trim()) {
$input.removeClass('error');
}
});
防止重複點擊
$('#submit').on('click', function () {
var $btn = $(this);
if ($btn.data('loading')) return;
$btn.data('loading', true).text('處理中...');
// 模擬 Ajax
setTimeout(function () {
$btn.data('loading', false).text('送出');
}, 2000);
});
快捷鍵
$(document).on('keydown', function (e) {
// Ctrl/Cmd + S 儲存
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
saveDocument();
}
// Esc 關閉 Modal
if (e.key === 'Escape') {
$('.modal').hide();
}
});
節流 (Throttle)
限制事件觸發頻率:
function throttle(fn, delay) {
var lastCall = 0;
return function () {
var now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn.apply(this, arguments);
}
};
}
// 捲動事件節流
$(window).on(
'scroll',
throttle(function () {
console.log('捲動位置:', $(this).scrollTop());
}, 100)
);
防抖 (Debounce)
延遲執行,在停止觸發後才執行:
function debounce(fn, delay) {
var timer;
return function () {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}
// 搜尋輸入防抖
$('#search').on(
'input',
debounce(function () {
var query = $(this).val();
console.log('搜尋:', query);
// 執行搜尋
}, 300)
);