jQuery 事件處理 (Events)

jQuery 的事件處理函式大都提供兩種用途,一種是呼叫帶有參數的函式 - 綁定事件處理函式;另一種則是呼叫不帶有參數的函式 - 觸發該事件

帶有參數,例如,綁定所有段落觸發 click 事件時,將背景顏色改為藍色:

$('p').click(function() {
  $(this).css('background-color', 'blue');
});

不帶有參數,例如,觸發所有段落的 click 事件:

$('p').click();
事件處理函數中的 this 為被觸發的「DOM元素」,而非 jQuery 物件。

上述的程式碼,我們用到 jQuery 定義好的 click 函式來處理 click event,然而 jQuery 也同樣對 DOM 其它的 event 都有相關的函式,如以下的 jQuery 事件函式也是同樣的使用方法:

事件觸發條件
blur物件失去焦點時
change物件內容改變時
click滑鼠點擊物件時
dblclick滑鼠連點二下物件時
error當圖片或文件下載產生錯誤時
focus當物件被點擊或取得焦點時
keydown按下鍵盤按鍵時
keypress按下並放開鍵盤按鍵後
keyup按下並放開鍵盤按鍵時
load網頁或圖片完成下載時
mousedown按下滑鼠按鍵時
mousemove介於over跟out間的滑鼠移動行為
mouseout滑鼠離開某物件四周時
mouseover滑鼠離開某物件四周時
mouseup放開滑鼠按鍵時
resize當視窗或框架大小被改變時
scroll當捲軸被拉動時
select當文字被選取時
submit當按下送出按紐時
beforeunload當使用者關閉 (或離開) 網頁之前
unload當使用者關閉 (或離開) 網頁之後

jQuery 的 event object

對於所有的 jQuery event handler,你都能傳入一個參數作為 event 物件,如下例:

$(document).click(function(event) {
  alert(event.pageX);
});

上面這個例子是用來取得滑鼠游標相對於頁面的位置,重點是!這段程式碼在IE上也能執行 (註:原本的 IE 瀏覽器事件無 pageX 屬性)。Why? 因為 jQuery 又事先幫你解決掉事件處理中跨瀏覽器的問題了 - 「jQuery 修改了 event object 以使其符合 W3C 的標準」,所以從此你不用再重複 if (window.event),你只需了解並使用標準的事件屬性!

jQuery 的事件綁定處理函式

.hover(handlerIn, handlerOut)

$(selector).hover(handlerIn, handlerOut) 其實是 $(selector).mouseenter(handlerIn).mouseleave(handlerOut); 的簡化版。

當滑鼠移動到一個匹配的元素上面時,會觸發第一個函數 (handlerIn);當滑鼠移出該元素時,會觸發第二個函數 (handlerOut)。

例如,滑鼠移進 li 元素時,在後面加上 ***,移出時再刪掉:

$('li').hover(
  function() {
    $(this).append($('<span> ***</span>'));
  }, function() {
    $(this).find('span:last').remove();
  }
);

.bind(eventType, handler) unbind(eventType)

除了直接用特定的事件函式來綁定事件 (i.e. .click()),你也可以用 .bind() 來做。

例如,滑鼠點 foo 元素時跳出 alert 訊息:

$('#foo').bind('click', function() {
  alert('User clicked on foo.');
});

.unbind() 用來移除事件處理函式,例如:

// 移除 foo 元素上所有綁定的事件處理函式
$('#foo').unbind();
// 只移除 foo 元素上所有綁定的 click 事件處理函式
$('#foo').unbind('click');

// 只移除特定事件的特定處理函式
var handler = function() { alert('hi'); };
$('#foo').bind('click', handler);
$('#foo').unbind('click', handler);

bind 及 unbind 還可以讓我們達到 自訂事件處理 的功能:

$('#foo').bind('myEvent', doSomething);

如上,我們自訂一個叫作 myEvent 的事件,但這不是 DOM 標準事件啊,怎麼觸發它?答案是用接著會談到的 "trigger" 函式來觸發 myEvent。

.trigger(eventType [, extraParameters])

觸發事件,其中 extraParameters 為要傳給事件處理函式的參數 (一個陣列或物件)。

例如,觸發上面的自訂 myEvent 事件:

$('#foo').trigger('myEvent');

當然也可以用來觸發一般事件:

$('#foo').trigger('click');

.one(events, handler)

如果只是觸發 "一次" 事件,就使用 one 函式來作 bind 的動作,當該事件被觸發一次之後就會自己自動 unbind。

$('#foo').one('click', function() {
  alert('This will be displayed only once.');
});

.on() .off()

從 jQuery 1.7 開始 .on() 是主要推薦使用的事件處理綁定函式,也用來取代 .bind(), .live().delegate()

.bind() 的缺點在於 bind 是直接將事件處理函式直接綁定在選取的元素上面,所以在綁定當時,元素必須是已經存在 DOM 中的!

例如,這樣子是沒用的:

// 這時頁面上還沒有 #foo 這元素
$('#foo').bind('click', function() { alert('hi'); });
$(document.body).append('<button id="foo">hi</button>');
// 點 button 不會有 alert!

.on() 充分利用瀏覽器事件傳播 (event bubbling) 的原理和事件委任 (event delegation) 的技巧!

在 IE8 以下,像是 change 和 submit 事件是不會 bubble 的,但 jQuery 也在底層幫你處理掉這個問題了 (yay cross-browser)!

因為事件委任的技巧讓 .on() 可以用來綁定事件處理在現在已經存在或還沒存在的 DOM 元素,像是你可以綁在 $(document).on() 監聽 DOM 的所有事件。

利用 event delegation 還有一個效能上的優點,你不用在一堆元素上各別綁定事件處理 (例如綁上千個元素,效能差),你可以只綁在 document 或 container element 上面,獲得顯著的效能改善。

例如,綁一堆 click 事件處理在 #dataTable tbody 下面的一堆 tr 元素上:

$('#dataTable tbody tr').on('click', function() {
  // 這裡的 this 指向 #dataTable tbody tr 這一個 DOM 元素
  // 用 $() 將 this 轉成 jQuery object
  console.log( $(this).text() );
});

V.S. 綁一個 click 事件處理在 #dataTable tbody 上,然後監聽從 tr bubble 上來的 click 事件:

$('#dataTable tbody').on('click', 'tr', function() {
  // 這裡的 this 指向符合 selector 的 DOM 元素,也就是 tr
  console.log( $(this).text() );
});

.on() 的第三個參數,可以用來傳資料進事件處理函式:

function greet(event) {
  alert('Hello ' + event.data.name);
}

$('button').on('click', {
  name: 'Karl'
}, greet);

$('button').on('click', {
  name: 'Addy'
}, greet);

用 .off() 來移除事件處理函式,例如:

// 移除所有 p 元素的事件處理
$('p').off();
// 移除所有 p 元素的 click 事件處理
$('p').off('click');
// 移除 #foo 的 click 事件委任
$('p').off('click', '#foo');

還可以用空白隔開多個事件,同時綁定事件處理:

$('#cart').on('mouseenter mouseleave', function(event) {
  $(this).toggleClass('active');
});

替事件命名 (Namespacing events)

我們雖然可以用 unbind / off 來移除事件,但是它會一次移除全部綁定的事件處理耶,何解?答案是利用 jQuery 提供的 Namespacing events!看個例子就明白它是什麼:

// 綁定事件處理函式;並幫這事件命名為 name
// 事件名稱要接在句點 . 後面
$('#foo').on('click.name', function() { alert(1); });
$('#foo').on('click.name', function() { alert(2); });
// 觸發所有名稱是 name 的事件
$('#foo').trigger('.name');
// 移除所有名稱是 name 的事件
$('#foo').off('.name');

Namespacing events 簡單的說就是幫事件取個名字 (namespace),了解吧 :)

$(document).ready(function() {})

最後,但是很重要也很常用的。jQuery 中,大部分的操作都基於 HTML DOM,所以我們必須確定頁面文件已經完全下載好才開始執行你的程式,jQuery 提供下面這個函式來處理 DOM ready 事件 (DOMContentLoaded):

$(document).ready(function() {
  // 這裡放你要執行的程式碼
});

你也可以這樣寫:

$(function() {
  // 這裡放你要執行的程式碼
});
jQuery 的 DOM ready event 是等 HTML DOM 準備好就可以開始執行程式,不像一般常用的 window.onload 要連圖片、外部檔案等全部都下載完畢才會觸發 onload 事件。

DOMContentLoaded & 外部樣式表的問題?

如上述,DOMContentLoaded 事件的觸發不必等到圖片檔案等下載完畢,但這裡有個問題就是,那需要等到 "外部樣式表 (External CSS Styles)" 下載完畢嗎?關於這一點,各家瀏覽器對於 DOMContentLoaded 事件的觸發也的確不一致,在 Opera 中瀏覽器不會等外部樣式表下載完即觸發 DOM ready;而 Firefox 則會等到外部 CSS 都加載完畢。因此若你有在 jQuery ready 事件中改變了元素的 CSS 屬性,對於 Opera,你修改的屬性很可能會被接著加載的外部樣式表覆蓋過去,這也就造成跨瀏覽器處理的問題。但如果你無法避免在頁面初始階段改變 CSS 屬性,這問題我們可以利用 load 事件來避開,以確定事件的觸發會在外部樣式表下載完畢之後:

$(window).load(function() {
  // Run code
});