JavaScript和HTML的互動是通過事件實現的。JavaScript採用非同步事件驅動編程模型,當文檔、瀏覽器、元素或與之相關對象發生特定事情時,瀏覽器會產生事件。如果JavaScript關注特定類型事件,那麼它可以註冊當這類事件發生時要調用的控制代碼。
事件流
事件流描述的是從頁面中接收事件的順序,比如有兩個嵌套的div,點擊了內層的div,這時候是內層的div先出發click事件還是外層先觸發?目前主要有三種模型
IE的事件冒泡:事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的元素
Netscape的事件捕獲:不太具體的節點更早接收事件,而最具體的元素最後接收事件,和事件冒泡相反
DOM事件流:DOM2級事件規定事件流包括三個階段,事件捕獲階段,處於目標階段,事件冒泡階段,首先發生的是事件捕獲,為截取事件提供機會,然後是實際目標接收事件,最後是冒泡句階段。
Opera、Firefox、Chrome、Safari都支援DOM事件流,IE不支援事件流,只支援事件冒泡
如有以下html,點擊div地區
複製代碼 代碼如下:<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Test Page</title>
</head>
<body>
<div>
Click Here</div>
</body>
</html>
事件處理常式(handler)
我們也稱之為事件接聽程式(listener),事件就是使用者或瀏覽器自身執行的某種動作。比如click、load、moseover等,都是事件類型(俗稱事件名稱),而響應某個事件的方法就叫做事件處理常式或者事件監聽器或者事件控制代碼,事件處理常式名字是:on+事件類型。
瞭解了這些,我們看看如何給元素添加事件處理常式
HTML事件處理常式元素支援的每個事件都可以使用一個相應事件處理常式同名的HTML 屬性指定。這個屬性的值應該是可以執行的JavaScript代碼,我們可以為一個button添加 click事件處理常式
複製代碼 代碼如下:<input type="button" value="Click Here" onclick="alert('Clicked!');" />
在HTML事件處理常式中可以包含要執行的具體動作,也可以調用在頁面其它地方定義的指令碼,剛才的例子可以寫成這樣
複製代碼 代碼如下:<input type="button" value="Click Here" onclick="showMessage();" />
<script type="text/javascript">
function showMessage() {
alert('Clicked!');
}
在HTML中指定事件處理常式書寫很方便,但是有兩個缺點。
首先,存在載入順序問題,如果事件處理常式在html代碼之後載入,使用者可能在事件處理常式還未載入完成時就點擊按鈕之類的觸發事件,存在時間差問題。
其次,這樣書寫html代碼和JavaScript代碼緊密耦合,維護不方便。
JavaScript指定事件處理常式通過JavaScript指定事件處理常式就是把一個方法賦值給一個元素的事件處理常式屬性。每個元素都有自己的事件處理常式屬性,這些屬性名稱通常為小寫,如onclick等,將這些屬性的值設定為一個方法,就可以指定事件處理常式,如下
複製代碼 代碼如下:<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.onclick = function showMessage() {
alert(this.id);
};
</script>
這樣處理,事件處理常式被認為是元素的方法,事件處理常式在元素的範圍下運行,this就是當前元素,所以點擊button結果是:btnClick
這樣還有一個好處,我們可以刪除事件處理常式,只需把元素的onclick屬性賦為null即可
DOM2事件處理常式DOM2級事件定義了兩個方法用於處理指定和刪除事件處理常式的操作:addEventListener和removeEventListener。所有的DOM節點都包含這兩個方法,並且它們都接受三個參數:事件類型、事件處理方法、一個布爾值。最後布爾參數如果是true表示在捕獲階段呼叫事件處理常式,如果是false,則是在事件冒泡階段處理。
剛才的例子我們可以這樣寫
複製代碼 代碼如下:<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.addEventListener('click', function() {
alert(this.id);
}, false);
</script>
上面代碼為button添加了click事件的處理常式,在冒泡階段觸發,與上一種方法一樣,這個程式也是在元素的範圍下運行,不過有一個好處,我們可以為click事件添加多個處理常式複製代碼 代碼如下:<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.addEventListener('click', function() {
alert(this.id);
}, false);
btnClick.addEventListener('click', function() {
alert('Hello!');
}, false);
</script>
這樣兩個事件處理常式會在使用者點擊button後按照添加順序依次執行。
通過addEventListener添加的事件處理常式只能通過removeEventListener移除,移除時參數與添加的時候相同,這就意味著剛才我們添加的匿名函數無法移除,因為匿名函數雖然方法體一樣,但是控制代碼卻不相同,所以當我們有移除事件處理常式的時候可以這樣寫
複製代碼 代碼如下:<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
var handler=function() {
alert(this.id);
}
btnClick.addEventListener('click', handler, false);
btnClick.removeEventListener('click', handler, false);
</script>
下面就是老生常談的IE相容性問題了。。。
IE並不支援addEventListener和removeEventListener方法,而是實現了兩個類似的方法attachEvent和detachEvent,這兩個方法都接收兩個相同的參數,事件處理常式名稱和事件處理常式方法,由於IE指支援事件冒泡,所以添加的程式會被添加到冒泡階段。
使用attachEvent添加事件處理常式可以如下
複製代碼 代碼如下:<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
var handler=function() {
alert(this.id);
}
btnClick.attachEvent('onclick' , handler);
</script>
結果是undefined,很奇怪,一會兒我們會介紹到
使用attachEvent添加的事件處理常式可以通過detachEvent移除,條件也是相同的參數,匿名函數不能被移除。
複製代碼 代碼如下:<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
var handler=function() {
alert(this.id);
}
btnClick.attachEvent('onclick', handler);
btnClick.detachEvent('onclick', handler);
</script>
跨瀏覽器的事件處理常式
前面內容我們可以看到,在不同的瀏覽器下,添加和移除事件處理常式方式不相同,要想寫出跨瀏覽器的事件處理常式,首先我們要瞭解不同的瀏覽器下處理事件處理常式的區別
在添加事件處理常式事addEventListener和attachEvent主要有幾個區別
1. 參數個數不相同,這個最直觀,addEventListener有三個參數,attachEvent只有兩個,attachEvent添加的事件處理常式只能發生在冒泡階段,addEventListener第三個參數可以決定添加的事件處理常式是在捕獲階段還是冒泡階段處理(我們一般為了瀏覽器安全色性都設定為冒泡階段)
2. 第一個參數意義不同,addEventListener第一個參數是事件類型(比如click,load),而attachEvent第一個參數指明的是事件處理函數名稱(onclick,onload)
3. 事件處理常式的範圍不相同,addEventListener得範圍是元素本身,this是指的觸發元素,而attachEvent事件處理常式會在全域變數內運行,this是window,所以剛才例子才會返回undefined,而不是元素id
4. 為一個事件添加多個事件處理常式時,執行順序不同,addEventListener添加會按照添加順序執行,而attachEvent添加多個事件處理常式時順序無規律(添加的方法少的時候大多是按添加順序的反順序執行的,但是添加的多了就無規律了),所以添加多個的時候,不依賴執行順序的還好,若是依賴於函數執行順序,最好自己處理,不要指望瀏覽器
瞭解了這四點區別後我們可以嘗試寫一個瀏覽器安全色性比較好的添加事件處理常式方法
複製代碼 代碼如下:function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node.attachEvent('on' + type, handler, );
return true;
}
return false;
}
這樣,首先我們解決了第一個問題參數個數不同,現在三個參數,採用事件冒泡階段觸發,第二個問題也得以解決,如果是IE,我們給type添加上on,第四個問題目前還沒有解決方案,需要使用者自己注意,一般情況下,大家也不會添加很多事件處理常式,試試這個方法感覺很不錯,但是我們沒有解決第三個問題,由於處理常式範圍不同,如果handler內有this之類操作,那麼就會出錯在IE下,實際上大多數函數都會有this操作。
複製代碼 代碼如下:function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node.attachEvent('on' + type, function() { handler.apply(node); });
return true;
}
return false;
}
這樣處理就可以解決this的問題了,但是新的問題又來了,我們這樣等於添加了一個匿名的事件處理常式,無法用detachEvent取消事件處理常式,有很多解決方案,我們可以借鑒大師的處理方式,jQuery創始人John Resig是這樣做的
複製代碼 代碼如下:function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node['e' + type + handler] = handler;
node[type + handler] = function() {
node['e' + type + handler](window.event);
};
node.attachEvent('on' + type, node[type + handler]);
return true;
}
return false;
}
在取消事件處理常式的時候
複製代碼 代碼如下:function removeEvent(node, type, handler) {
if (!node) return false;
if (node.removeEventListener) {
node.removeEventListener(type, handler, false);
return true;
}
else if (node.detachEvent) {
node.detachEvent('on' + type, node[type + handler]);
node[type + handler] = null;
}
return false;
}
John Resig很巧妙地利用了閉包,看起來很不錯。
事件對象
在觸發DOM上的某個事件的時候會產生一個事件對象event,這個對象包含著所有與事件有關的資訊,包括產生事件的元素、事件類型等相關資訊。所有瀏覽都支援event對象,但支援方式不同。
DOM中的事件對象相容DOM的瀏覽器會產生一個event對象傳入事件處理常式中。應用一下剛才我們寫的addEvent方法
複製代碼 代碼如下:var btnClick = document.getElementById('btnClick');
addEvent(btnClick, 'click', handler);
點擊button的時候我們可以看到彈出內容是click的彈窗
event對象包含與建立它的特定事件有關的屬性和方法,觸發事件的類型不同,可用的屬性和方法也不同,但是所有事件都會包含
屬性/方法 |
類型 |
讀/寫 |
說明 |
bubbles |
Boolean |
唯讀 |
事件是否冒泡 |
cancelable |
Boolean |
唯讀 |
是否可以取消事件的預設行為 |
currentTarget |
Element |
唯讀 |
事件處理常式當前處理元素 |
detail |
Integer |
唯讀 |
與事件相關細節資訊 |
eventPhase |
Integer |
唯讀 |
事件處理常式階段:1 捕獲階段,2 處於目標階段,3 冒泡階段 |
preventDefault() |
Function |
唯讀 |
取消事件預設行為 |
stopPropagation() |
Function |
唯讀 |
取消事件進一步捕獲或冒泡 |
target |
Element |
唯讀 |
事件的目標元素 |
type |
String |
唯讀 |
被觸發的事件類型 |
view |
AbstractView |
唯讀 |
與事件關聯的抽象視圖,等同於發生事件的window對象 |
在事件處理常式內部,this始終等同於currentTarget,而target是事件的實際目標。
要阻止事件的預設行為,可以使用preventDefault()方法,前提是cancelable值為true,比如我們可以阻止連結導航這一預設行為
複製代碼 代碼如下:document.getElementsByTagName('a').onclick = function (e) {
e.preventDefault();
}
stopPaopagation()方法可以停止事件在DOM層次的傳播,即取消進一步的事件捕獲或冒泡。我們可以在button的事件處理常式中調用stopPropagation()從而避免註冊在body上的事件發生
複製代碼 代碼如下:var handler = function (e) {
alert(e.type);
e.stopPropagation();
}
addEvent(document.body, 'click', function () { alert('Clicked body')});
var btnClick = document.getElementById('btnClick');
addEvent(btnClick, 'click', handler);
若是注釋掉e.stopPropagation(); 在點擊button的時候,由於事件冒泡,body的click事件也會觸發,但是調用這句後,事件會停止傳播。
IE中的事件對象訪問IE中的event對象有幾種不同的方式,取決於指定事件處理常式的方法。直接為DOM元素添加事件處理常式時,event對象作為window對象的一個屬性存在
複製代碼 代碼如下:var handler = function () {
var e = window.event;
alert(e.type);
}
var btnClick = document.getElementById('btnClick');
btnClick.onclick = handler;
我們通過window.event取得了event對象,並檢測到了其類型,可是如果事件處理常式是通過attachEvent添加的,那麼就會有一個event對象被傳入事件處理常式中
複製代碼 代碼如下:var handler = function (e) {
alert(e.type);
}
var btnClick = document.getElementById('btnClick');
attachEvent(btnClick, handler);
當然這時候也可以通過window對象訪問event,方便起見,我們一般會傳入event對象,IE中所有的事件都包含以下屬性方法
屬性/方法 |
類型 |
讀/寫 |
說明 |
cancelBulle |
Boolean |
讀/寫 |
預設為false,設定為true後可以取消事件冒泡 |
returnValue |
Boolean |
讀/寫 |
預設為true,設為false可以取消事件預設行為 |
srcElement |
Element |
唯讀 |
事件的目標元素 |
type |
String |
唯讀 |
被觸發的事件類型 |
跨瀏覽器的事件對象雖然DOM和IE的event對象不同,但基於它們的相似性,我們還是可以寫出跨瀏覽器的事件對象方案
複製代碼 代碼如下:function getEvent(e) {
return e || window.event;
}
function getTarget(e) {
return e.target || e.scrElement;
}
function preventDefault(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
function stopPropagation(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
常用HTML事件
有一些HTML事件我們會經常用到,這些事件不一定與使用者操作有關,這裡只是簡單提及,詳細用法大家就得百度Google了
1.load:當頁面完全載入後在window上觸發,當映像載入完成後在img元素上觸發,或當嵌入內容載入完成時,在object元素上觸發
2.unload:頁面完全卸載後在window上觸發,或嵌入內容卸載後在object元素觸發
3.select:使用者選擇文字框中的字元時觸發
4.change:文字框焦點變化後其值改變時觸發
5.submit:使用者提交表單的時候觸發
6.resize:視窗或架構大小變化的時候在window上觸發
7.scrool:使用者滾動帶捲軸的元素時,在該元素上觸發
8.focus:頁面或元素獲得焦點時在window及相應元素上觸發
9.blur:頁面或元素失去焦點時在window及相應元素上觸發
10.beforeunload:頁面卸載前在window上觸發
11.mousewheel:不算HTML的,當使用者通過滑鼠滾輪與頁面互動,在垂直方向滾動頁面時觸發