事件流
JavaScript與HTML之間的交互是通過事件實現(xiàn)的。事件,就是文檔或瀏覽器窗口中發(fā)生的一些特定的交互瞬間。可以使用偵聽器(或處理程序)來預定事件,以便事件發(fā)生時執(zhí)行相應的代碼。這種在傳統(tǒng)軟件工程中被成為觀察員模式的模型,支持頁面的行為(js代碼)與頁面的外觀(HTML和CSS代碼)之間的松散耦合。
事件最早是在IE3和Netscape Navigator2(以下簡稱網(wǎng)景)中出現(xiàn)的,當時作為分擔服務器運算負載的一種手段。在IE4和網(wǎng)景4發(fā)布時,這兩種瀏覽器都提供了相似不相同的API,這些API并存經(jīng)過了好幾個主要版本,DOM2級規(guī)范開始嘗試以一種符合邏輯的方式來標準化DOM事件。IE9、Firefox、Opera、Safari和Chrome全部已實現(xiàn)了DOM2級事件模塊的核心部分。IE8是最后一個仍然使用其專有事件系統(tǒng)的主要瀏覽器。
定義與由來
當瀏覽器發(fā)展到第四代時,瀏覽器開發(fā)團隊遇到了一個很有意思的問題:頁面的哪一部分會擁有某個特定的事件?想象一下,在一張紙上畫一個同心圓,如果你把手指放在圓心上,那么你的手指指向的不是一個圓,而是紙上所有的圓。兩家公司的瀏覽器開發(fā)團隊看待瀏覽器事件方面還是一致的。如果你單擊了某個按鈕,他們都認為單擊事件不僅僅發(fā)生在按鈕上。換句話說,在單擊按鈕的同時,你也單擊了按鈕的容器元素,甚至也單擊了整個頁面。
事件流描述的是從頁面中接受事件的順序。但有意思的是,IE和網(wǎng)景開發(fā)團隊居然提出了差不多完全相反的事件流的概念。IE的事件流是事件冒泡,而網(wǎng)景的事件流是事件捕獲流。
事件冒泡
IE的事件流叫做事件冒泡(event bubbling),即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節(jié)點)接收,然后逐級向上傳播到較為不具體的節(jié)點(文檔)。以下面的HTML頁面為例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
如果單擊了頁面中的<div>元素,那么這個click事件會按照如下順序傳播:
- <div>
- <body>
- <html>
- document
也就是說,click事件首先在<div>元素上發(fā)生,而這個元素就是我們單擊的元素。然后,click事件沿DOM數(shù)向上傳播,在每一級節(jié)點上都會發(fā)生,直至傳播到document對象。
所有瀏覽器都支持事件冒泡,但在具體實現(xiàn)上還是有一些差別。IE5.5及更早版本會跳過<html>元素直接到document對象。IE9、Chrome、Safari則將事件一直冒泡到window對象。
事件捕獲
網(wǎng)景團隊提出的另一種事件流叫做事件捕獲(event capturing)。事件捕獲的思想是不太具體的節(jié)點應該更早接收到事件,而最具體的節(jié)點應該最后接收到事件。事件捕獲的用意在于在事件到達預定目標之前捕獲它。如果仍以之前HTML代碼演示捕獲例子,那么單擊click元素就會以下列順序觸發(fā)click事件:
- document
- <html>
- <body>
- <div>
在事件捕獲過程中,document對象首先接收到click事件,然后事件沿DOM數(shù)依次向下,一直傳播到事件具體目標,即<div>元素。

雖然事件捕獲是網(wǎng)景唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox目前也都支持這種事件流模型。盡管”DOM2級事件“規(guī)范要求事件應該從document對象開始傳播,但這些瀏覽器都是從window對象開始捕獲事件的。
由于老版本瀏覽器不支持,很少有人使用事件捕獲,一般都使用事件冒泡。
DOM事件流
DOM2級事件規(guī)定的事件流包括三個階段:事件捕獲階段、處于目標階段、事件冒泡階段。首先發(fā)生的是事件捕獲,為截獲事件提供了機會。然后是實際的目標接收到事件。最后一個階段是冒泡階段,可以在這個階段對事件做出響應。以前面簡單的HTML頁面為例,單擊<div>元素會按照下圖所示順序觸發(fā)事件。

在DOM事件流中,實際的目標<div>元素在捕獲階段不會接收到事件。這意味在捕獲階段,事件從document到<html>再到<body>后就停止了。下一個階段是”處于目標“階段,于是事件在<div>上發(fā)生,并在事件處理中被看成冒泡階段的一部分。然后,冒泡階段發(fā)生,事件又傳播回文檔。
IE8及更早版本不支持DOM事件流。
事件處理程序
事件就是用戶或瀏覽器自身執(zhí)行的某種動作。諸如:click、load和mouseover,都是事件的名字。而響應某個個事件的函數(shù)叫做事件處理程序(或事件偵聽器)。
其實JS與HTML的交互就是事件處理程序執(zhí)行的結果。
事件處理程序的名字以"on"開頭,因此click事件的事件處理程序(click事件偵聽器)就是onclick,load事件的事件處理程序(load事件偵聽器)就是onload。
為事件指定處理程序的方式有好幾種(面試)
HTML事件處理程序
某個元素支持的每種事件,都可以使用一個與相應事件處理程序同名的HTML特性來指定。這個特性的值應該是能夠執(zhí)行的JavaScript代碼。如:
<input type="button" value="Click Me" onclick="alert('Clicked')">
當單擊這個按鈕時,就會顯示一個警告框。這個操作是通過指定onclick特性一些JavaScript代碼作為它的值來定義的。由于這個值是JavaScript,因此不能在其中使用未經(jīng)轉義的HTML語法字符。為了避免使用HTML實體,這里使用單引號,如果想要使用雙引號,可以這樣改寫:
<input type="button" value="Click Me" onclick="alert("Clicked")">
在HTML中定義的事件處理程序可以包含要執(zhí)行的具體動作,也可以調(diào)用在頁面其他地方定義的腳本:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Event Bubbling Example</title>
<script src="./js/fuck.js"></script>
</head>
<body>
<input type="button" value="Click Me" onclick="showMessage()">
<script>
function showMessage() {
alert("hello world");
}
</script>
</body>
</html>
單擊按鈕調(diào)用showMessage()函數(shù),這個函數(shù)是在一個獨立<script>元素中定義的,當然也可以被包含在一個外部文件中。事件處理程序中的代碼在執(zhí)行時,有權訪問全局作用域中任何代碼。
這樣指定事件處理程序具有一些獨到之處。首先,這樣會創(chuàng)建一個封裝著元素屬性值的函數(shù)。這個函數(shù)中有一個局部變量event,也就是事件對象:
<input type="button" value="Click Me" onclick="console.log(event.type)">
<!-- 輸出click -->
event為事件對象,我們打印出來看看里面有啥
<input type="button" value="Click Me" onclick="console.log(event)">
null: MouseEvent {isTrusted: true, screenX: 261, screenY: 236, clientX: 38, clientY: 17, …}
altKey: false
bubbles: true
button: 0
buttons: 0
cancelable: true
cancelBubble: false
clientX: 38
clientY: 17
composed: true
ctrlKey: false
currentTarget: null
defaultPrevented: false
detail: 1
eventPhase: 0
fromElement: null
isTrusted: true
layerX: 38
layerY: 17
metaKey: false
movementX: 0
movementY: 0
offsetX: 28
offsetY: 5
pageX: 38
pageY: 17
path: Array(5) [input, body, html, …]
relatedTarget: null
returnValue: true
screenX: 261
screenY: 236
shiftKey: false
sourceCapabilities: InputDeviceCapabilities {firesTouchEvents: false}
srcElement: input
target: input
timeStamp: 3469.8000000207685
toElement: input
type: "click"
view: Window {postMessage: , blur: , focus: , …}
which: 1
x: 38
y: 17
__proto__: MouseEvent {screenX: <accessor>, screenY: <accessor>, clientX: <accessor>, …}
通過event變量,可以直接訪問事件對象,你不用自己定義它,也不用從函數(shù)的參數(shù)列表中讀取。在這個函數(shù)內(nèi)部,this值等于事件的目標元素。如:
<input type="button" value="Click Me" onclick="console.log(this.value)">
<!-- 輸出 "Click Me"-->
關于這個動態(tài)創(chuàng)建的函數(shù),另一個有意思的地方是它擴展作用域的方式,在這個函數(shù)內(nèi)部,可以像訪問局部變量一樣訪問document及該元素本身的成員。這個函數(shù)使用with像下面這樣擴展作用域:
function() {
with(document) {
with(this) {
// 元素屬性值
}
}
}
這樣一來,事件處理程序要訪問自己的屬性就容易多了。
<input type="button" value="Click Me" onclick="alert(value)">
<!-- 輸出 "Click Me" -->
總結(面試):
這種在HTML中指定事件處理程序有兩個缺點。
時差問題
用戶可能會在HTML元素一出現(xiàn)在頁面上就觸發(fā)相應的事件,但當時的事件處理程序有可能尚不具備執(zhí)行條件。
比如,將click事件處理函數(shù)放在HTML下方,頁面最底部,用戶在函數(shù)解析完成前激活了click事件,就會引發(fā)錯誤。為此,很多HTML事件處理程序都會被封裝在一個try-catch塊中,以便錯誤不會浮出水面,如:
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">
這樣,如果在showMessage()函數(shù)有定義之前單擊按鈕,用戶不會看到JavaScript錯誤,因為在瀏覽器有機會處理錯誤之前,錯誤就被捕獲了。
作用域問題
另一個缺點是,這樣擴展事件處理程序的作用域鏈在不同瀏覽器導致不同結果。不同JavaScript引擎遵循的標識符規(guī)則略有差異,很可能會在訪問非限定對象成員時出錯。
分離原則
通過HTML指定事件處理程序最后一個缺點是HTML與JavaScript代碼緊密耦合。如果要更換事件處理程序,就要改動HTML代碼和JavaScript代碼,而這正是開發(fā)人員摒棄HTML事件處理程序,轉而使用JavaScript指定事件處理程序的原因所在。
不要使用HTML指定事件處理程序
DOM0級事件處理程序
通過JavaScript指定事件處理方式的傳統(tǒng)方式,就是將一個函數(shù)賦值給一個事件處理程序屬性。這種為事件處理程序賦值的方法是在第四代Web瀏覽器中出現(xiàn)。
- 簡單
- 具有跨瀏覽器優(yōu)勢
每個元素(包括window和document)都有自己的事件處理程序屬性,這些屬性通常全部小寫,例如onclick。將這種屬性的值設置為一個函數(shù),就可以指定事件處理程序。
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
};
在此,我們通過文檔對象取得了一個按鈕的引用,然后為它指定了onclick事件處理程序。但要注意,在這些代碼運行以前不會指定事件處理程序,因此如果這些代碼在頁面中位于按鈕后面,就有可能在一段時間內(nèi)怎么單擊都沒有反應。
使用DOM0級方法指定的事件處理程序被認為是元素的方法,因此,這時候的事件處理程序是在元素的作用域中運行;換句話說,程序中的this引用當前元素。
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert(this.id); // "myBtn"
};
單擊按鈕顯示的是元素的ID,這個ID是通過this.id取得的。不僅僅是ID,實際上可以在事件處理程序中通過this訪問元素的任何屬性和方法。
以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。
也可以刪除通過DOM0級方法指定的事件處理程序。
btn.onclick = null; // 刪除事件處理程序
此時單擊按鈕不會有任何動作發(fā)生。
個人理解:沒有動作發(fā)生因為沒有事件處理程序,但是的確存在這個事件被觸發(fā)。只是我們沒有對這個事件發(fā)生時“做些什么”而已。
如果你使用HTML指定事件處理程序,那么onclick屬性的值就是一個包含著在同名HTML特性中指定的代碼的函數(shù)。而將相應的屬性設置為null,也可以刪除以這種方式指定的事件e處理程序。
DOM2級事件處理程序
"DOM2級事件"定義了兩個方法,用于處理指定和刪除事件處理程序的操作:addEventListener()和removeEventListener()。所有DOM節(jié)點中都包含這兩個方法,并且它們都接受3個參數(shù):要處理的事件名、作為事件處理程序的函數(shù)和一個布爾值。最后這個布爾值參數(shù)如果是true,表示在捕獲階段調(diào)用事件處理程序;如果是false,表示在冒泡階段調(diào)用事件處理程序。
按鈕上為click事件添加事件處理程序:
<input type="button" id="myBtn" value="Click Me">
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
上面的代碼為一個按鈕添加了onclick事件處理程序,而且該事件會在冒泡階段被觸發(fā)(因為最后一個參數(shù)false)。與DOM0級方法一樣,這里添加的事件處理程序也是在其依附的元素的作用域中運行。使用DOM2級方法添加事件處理程序的主要好處是可以添加多個事件處理程序。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
btn.addEventListener("click",function() {
alert("Hello World!");
}, false)
調(diào)用的事件處理程序會按照添加它們的順序觸發(fā),因此首先會顯示ID,而后顯示”Hello World!“消息。
通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
btn.removeEventListener("click", function () {
alert(this.id);
})
這樣行嗎?當然不行,兩個方法定義的事件處理函數(shù)根本就不是”一個東西“,即它們是兩個不相干的函數(shù)對象。
我們需要使用函數(shù)表達式。
var btn = document.getElementById("myBtn");
var handler = function() {
alert(this.id);
};
btn.addEventListener("click",handler,false);
btn.removeEventListener("click",handler,false);
大多數(shù)情況,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度的兼容各種瀏覽器。
不是特別需要,不建議在事件捕獲階段注冊事件處理程序。
IE事件處理程序(IE兼容方法)
IE實現(xiàn)了與DOM中類似的兩個方法:attachEvent()和detachEvent()
這兩個方法接受相同的兩個參數(shù):事件處理程序名稱和事件處理函數(shù)。由于IE8及更早版本只支持事件冒泡,所以通過attachEvent()添加的事件處理程序都會被添加到冒泡階段。
使用attachEvent()為按鈕添加一個事件處理程序:
btn.attachEvent("onclick",function() {
alert("Clicked");
});
注意,參數(shù)從"click"變?yōu)榱?onclick"。
IE中使用attachEvent()與使用DOM0級方法的主要區(qū)別在于事件處理程序的作用域。在使用DOM0級方法的情況下,事件處理程序會在其所屬元素的作用域內(nèi)運行;在使用attachEvent()方法的情況下,事件處理程序會在全局作用域下與西寧,因此this等于window。
btn.attachEvent("onclick",function() {
alert(this === window);
});
在編寫跨瀏覽器的代碼時,記住。
與addEventListener()類似,attachEvent()方法也可以用來為一個元素添加多個事件處理程序。
btn.attachEvent("onclick",function() {
alert(this === window);
});
btn.attachEvent("onclick",function() {
alert("Hello World!");
});
但是!這些事件處理程序不是以添加它們的順序來執(zhí)行的,而是以相反的順序被觸發(fā)
先是看到Hello World!,然后才是true。
使用detachEvent()移除事件處理程序和removeEventListener()是差不多的。
var btn = document.getElementById("myBtn");
var handler = function() {
alert("hello world!")
}
btn.attachEvent("onclick",handler);
btn.detachEvent("onclick",handler);
支持IE事件處理程序的瀏覽器有IE和Opera
跨瀏覽器的事件處理程序
var EventUtil = {
addHandler:function(element,type,handler) {
if (element.addEventListener) {
element.addEventListener(type,handler,false);
} else if (element.attachEvent) {
element.attachEvent("on"+typeo,handler);
} else {
element["on"+type] = handler;
}
},
removeHandler:function(element,type,handler) {
if (elemen.removeEventListener) {
element.removeEventListener(type,handler,false);
} else if (element.detachEvent) {
element.detachEvent("on"+type,handler);
} else {
element["on"+type] = null;
}
}
}
這連個方法都會檢測傳入元素是否存在DOM2級方法。如果存在,使用該方法:傳入事件類型、事件處理程序函數(shù)和第三個參數(shù)false(表示冒泡階段)。如果存在的是IE的方法,則采取第二種方案。
為了在IE8級更早版本中運行,此時的事件類型必須加上"on"前綴。
最后一種可能是使用DOM0級方法(在現(xiàn)在瀏覽器中,不會執(zhí)行這里的代碼)。此時,我們使用的是方括號語法來講屬性名指定為事件處理程序,或者將屬性設置為null。
var btn = document.getElementById("myBtn");
var handler = function() {
alert("Clicked");
}
EventUtil.addHandler(btn,"click",handler);
EventUtil.removeHandler(btn,"click",handler);
addHandler()和removeHandler()沒有考慮所有的瀏覽器問題,例如在IE中的作用域問題,不過,使用它們添加和移除事件處理程序還是足夠了。
此外要注意,DOM0級對每個事件只支持一個事件處理程序。不過現(xiàn)在只支持DOM0級的瀏覽器應該很少了。
事件對象
在觸發(fā)DOM上的事件時,會產(chǎn)生一個事件對象event,這個對象中包含所有與事件有關的信息。包活導致事件的元素、事件的類型及其他與特定事件相關的信息。例如,鼠標操作導致的事件對象中,會包含鼠標位置的信息,鍵盤操作導致的事件對象中,會包含與按下的鍵有關的信息。
所有的瀏覽器都支持event對象,但支持方式不同。
DOM中的事件對象
兼容DOM的瀏覽器會將一個event對象傳入到事件處理程序中。無論指定事件處理程序時使用什么方法(DOM0級或DOM2級),都會傳入event對象。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.type); // "click"
};
btn.addEventListener("click",function(event) {
alert(event.type); // "click<"
},false);
event屬性始終都會包含被觸發(fā)的事件類型,例如"click"。
在通過HTM特性指定事件處理程序時,變量event中保存著event對象。
<input type="button" id="myBtn" value="Click Me" onclick="alert(event.type)">
以這種方式提供event對象,可以讓HTML特性事件處理程序與JavaScript函數(shù)執(zhí)行相同的操作。
event對象包含與創(chuàng)建它的特定事件有關的屬性和方法。觸發(fā)的事件類型不一樣,可用的屬性和方法也不一樣。不過所有屬性都會有一些共有的成員。
/*
屬性/方法 類型 讀/寫 說明
bubbles Boolean 只讀 表明事件是否冒泡
cancelable Boolean 只讀 表明是否可以取消事件的默認行為
currentTarget Element 只讀 其事件處理程序當前正在處理事件的那個元素
defaultPrevented Boolean 只讀 為true表示已經(jīng)調(diào)用了preventDefault()
detail Integer 只讀 與事件有關的細節(jié)信息
eventPhase Integer 只讀 調(diào)用事件處理程序的階段:1捕獲階段、2"處于目標"、3表示冒泡階段
preventDefault Function 只讀 取消事件的默認行為。如果cancleable是true,則可以使用這個方法
stopImmediatePropagation() Function 只讀 取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程序被調(diào)用
stopPropagation() Function 只讀 取消事件的進一步捕獲或冒泡。如果bubbles為true,則可以使用這個方法
target Element 只讀 事件的目標
trusted Boolean 只讀 為true表示目標是瀏覽器生成的。為false表示事件是由開發(fā)人員通過JavaScript創(chuàng)建的
type String 只讀 被觸發(fā)的事件的類型
view AbstractView 只讀 與事件關聯(lián)的抽象視圖。等同于發(fā)生事件的window對象。
*/
在事件處理程序內(nèi)部,對象this始終等于cureentTarget的值,而target則只包含事件的實際目標。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.currentTarget === this); // true
alert(event.target === this); // true
}
這個例子檢測了currentTarget和target與this的值。由于click事件的目標是按鈕,因此這三個值是相等的。
如果事件處理程序存在于按鈕的父節(jié)點中(例如document.body),那么情況就有所變化。
document.body.onclick = function(event) {
alert(event.currentTarget === document.body); // true
alert(this === document.body); // true
alert(event.target === document.getElementById("myBtn")); // true
}
當單擊這個例子中的按鈕時,this和currentTarget都等于document.body,因此事件處理程序是注冊到這個元素上的。然而,target元素卻等于按鈕元素,因為它是click事件真正的目標(用戶操作點擊了按鈕)。只是按鈕上沒有事件處理程序,沒喲函數(shù)執(zhí)行。結果click事件冒泡到了body,在那里事件得到處理。
在需要通過一個函數(shù)處理多個事件時,可以使用type屬性。
var btn = document.getElementById("myBtn");
var handler = function(event) {
switch(event.type) {
case "click":
alert("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseouot":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
例子定義了名為handler的函數(shù),用于處理三種事件:click、mouseover、mouseout。
當單擊按鈕時,出現(xiàn)一個警告框。當按鈕移動到按鈕上面時,背景顏色變化成紅色。當鼠標移動出按鈕時,背景顏色回復默認值。
這里通過檢測event.type屬性,讓函數(shù)能夠確定發(fā)生了什么事件,并執(zhí)行相應操作。
要阻止特定事件的默認行為,可以使用preventDefault()方法。例如,鏈接的默認行為就是在被單擊時導航到其href指定的URL。如果想阻止鏈接導航這一默認行為,那么通過onclick事件處理程序可以取消它。
var link = document.getElementById("myLink");
link.onclick = function(event) {
event.preventDefault();
};
另外只有cancelable屬性設置為true的事件,才可以使用preventDefault()來取消去默認行為。
另外,stopPropagation()方法用于立即停止事件在DOM層次中的傳播,即取消進一步的事件捕獲或冒泡。
例如,直接添加到一個按鈕的事件處理程序可以調(diào)用stopPropagation(),從而避免觸發(fā)注冊在document.body上面的事件處理程序。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
alert("Body clicked");
}
事件在目標階段被觸發(fā)后,不會傳播到document.body,因此就不會觸發(fā)注冊在這個元素上的onclick事件處理對象。
事件對象的eventPhase屬性,可以用來確定事件當前位于事件流的哪個階段。
- 捕獲階段:1
- 目標階段:2
- 冒泡階段:3
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.eventPhase); // 1
}
document.body.addEventListener("click",function(event) {
console.log(event.eventPhase);
},true); // 2
document.body.onclick = function(event) {
console.log(event.eventPhase);
}; // 3
首先執(zhí)行的事件處理程序是在捕獲階段觸發(fā)的添加到document.body的那一個,結果會彈出一個警告框顯示表示eventPhase的1。接著,會觸發(fā)在按鈕上注冊的事件處理程序,此時的eventPhase值為2。最后一個被觸發(fā)的事件處理程序,是在冒泡階段執(zhí)行的添加到document.body上的那一個,顯示eventPhase的值為3。
當eventPhase等于2時,this、target、currentTarget始終相等。
只有事件處理程序執(zhí)行期間,event對象才會存在;一旦事件處理程序執(zhí)行完成,event就會銷毀。
IE中的事件對象
與訪問DOM中的event對象不同,要訪問IE中的event對象有幾種不同方式,取決與執(zhí)行事件處理程序的方法。在使用DOM0級方法添加事件處理程序時,event對象作為window對象的一個屬性存在。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
var event = window.event;
alert(event.type); // "click"
};
在此,我們通過window.event取得了event對象,并檢測了被觸發(fā)事件的類型。
如果使用attachEvent()添加的,那么就會有一個event對象作為參數(shù)被傳入事件處理程中。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick",function(event) {
alert(event.type); // "click"
});
如果是通過HTML特性指定:
<input type="button" id="myBtn" value="Click Me" onclick="alert(event.type)">
IE的event對象同樣也包含與創(chuàng)建它的事件相關的屬性和方法。其中很多屬性和方法都有對應的或者相關的DOM屬性和方法。與DOM的event對象一樣,這些屬性和方法也會因為事件類型的不同而不同,但所有事件對象都有這些:
/*
屬性/方法 類型 讀/寫 說明
cancelBubble Boolean 讀/寫 默認值false,將其設置為true可以取消事件冒泡
returnValue Boolean 讀/寫 默認值為true,將其設置為false可以取消事件的默認行為
srcElement Element 只讀 事件的目標
type String 只讀 被觸發(fā)的事件的類型
/*
因為事件處理程序的作用域是根據(jù)它的方式來確定的,所以不能認為this會始終等于事件目標。故而,最好還是使用event.srcElement比較保險。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(window.event.srcElement === this); // true
};
btn.attachEvent("onclick",function(event) {
alert(event.srcElement === this); // false
});
在第一個事件處理程序中(使用DOM0級方法),srcElement屬性等于this,但在第二個事件處理程序中,這兩者的值不同。
returnValue屬性相當于DOM中的preventDefault()方法,它們的作用都是取消給定事件的默認行為,只要將returnValue設置為false,就可以阻止默認行為。
var link = document.getElementById("myLink");
link.onclick = function() {
window.event.returnValue = false;
};
相應的,cancelBubbles屬性與DOM中的stopPropagation()方法相同,都是用來停止事件冒泡的。由于IE不支持事件捕獲,因為只能取消事件冒泡;但stopPropagation()可以同時取消事件捕獲和冒泡。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function() {
alert("Body clicked");
};
跨瀏覽器事件對象
雖然DOM和IE中的event對象不同,但基于它們之間的相似性依舊可以拿出跨瀏覽器的方案來。
IE中event對象的全部信息和方法DOM對象中都有,只不過實現(xiàn)方式不一樣。
var EventUtil = {
addHandler: function (element, type, handler) { // 跨瀏覽器添加事件處理程序
// 省略
},
getEvent: function (event) { // 跨瀏覽器得到事件對象
return event ? event : window.event;
},
getTarget: function (event) { // 跨瀏覽器得到事件目標
return event.target || event.srcElement;
},
preventDefault: function (event) { // 跨瀏覽器阻止事件傳播(IE為取消事件冒泡)
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function (element, type, handler) { // 跨瀏覽器移除事件處理程序
// 省略
},
stopPropagation: function (event) { // 跨瀏覽器組織默認行為
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
事件類型
Web瀏覽器中可能發(fā)生的事件有很多類型。不同的事件類型具有不同的信息,而”DOM3及事件“規(guī)定了以下幾類事件。
- UI(User Interface)事件,當用戶與頁面上的元素交互時觸發(fā);
- 焦點事件,當元素獲得或失去焦點時觸發(fā);
- 鼠標事件,當用戶通過鼠標在頁面上執(zhí)行操作時觸發(fā);
- 滾輪事件,當使用鼠標(或類似設備)時觸發(fā);
- 文本事件,當在文檔中輸入文本時觸發(fā);
- 鍵盤事件,當用戶通過鍵盤在頁面上執(zhí)行操作時觸發(fā);
- 合成事件,當為IME(Input Method Editor 輸入法編輯器)輸入字符時觸發(fā);
- 變動(mutation)事件,當?shù)讓覦OM結構發(fā)生變化時觸發(fā)。
除了這幾類事件之外,HTML5也定義了一組事件,而有些瀏覽器還會在DOM和BOM中實現(xiàn)其他專有事件。這些專有的事件一般都是根據(jù)開發(fā)者需求定制的。
UI事件
UI事件指的是那些不一定與用戶操作有關的事件。這些事件在DOM規(guī)范出現(xiàn)之前,都是以這種或那種形式存在的,而在DOM規(guī)范中保留是為了向后兼容。現(xiàn)有的UI事件如下。
- DOMActivate:表示元素已經(jīng)被用戶操作(通過鼠標或鍵盤)激活,這個事件在DOM3級事件中被廢棄。
- load:當頁面完全加載后在window上面觸發(fā),當所有框架都加載完畢時在框架集上面觸發(fā),當圖像加載完畢時在<Img>元素上面觸發(fā),或者當嵌入的內(nèi)容加載完畢時在<object>元素上面觸發(fā)。
- unload:當頁面完全卸載后在window上面觸發(fā),當所有框架都卸載后在框架集上面觸發(fā),或者當嵌入的內(nèi)容卸載完畢后<object>元素上面觸發(fā)。
- abort:在用戶停止下載過程時,如果嵌入的內(nèi)容沒有加載完,則在<object>元素上面觸發(fā)。
- error:當發(fā)生JavaScript錯誤時在window上面觸發(fā),當無法加載圖像時在<img>元素上面觸發(fā),無法加載嵌入內(nèi)容時<object>上觸發(fā),或者當一或多個框架無法加載時在框架集上觸發(fā)。
- select:當用戶選擇<input>或<textarea>中一或多個字符時被觸發(fā)。
- resize:當窗口或框架的大小變化時在window或框架上面觸發(fā)。
- scroll:當用戶滾動帶滾動條的元素中的內(nèi)容時,在該元素上面觸發(fā)。<body>元素中包含所加載頁面的滾動條。
多數(shù)這些事件斗魚window對象或表單控件相關。
load事件
JavaScript中最常用 的一個事件就是load。當頁面完全加載后(包括所有圖像、JavaScript文件、CSS文件等外部資源),就會觸發(fā)window上面的load事件。有兩種定義onload事件處理程序的方式。
EventUtil.addHandler(window,"load",function(event) {
alert("Loaded!");
});
這是通過JavaScript來指定事件處理程序的方式,使用了之前跨瀏覽器的EventUtil對象。同樣的,這里也給事件傳入了一個event對象。這個event對象中不包括有關這個事件的任何附加信息,但在兼容DOM瀏覽器中,event.target屬性的值會被設置為document,而IE并不會為這個事件設置srcElement屬性。
第二種指定onload事件處理程序的方式是為body元素添加一個onload特性。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<title>Load Event Example</title>
<body onload="alert('Loaded!')">
</body>
</html>
一般來說,在window上面發(fā)生的任何事件都可以在body元素中通過相應的特性來指定,因為在HTML中無法訪問window元素。實際上,這只是為了保證向后兼容的一種權宜之計。建議使用JavaScript方式。
圖像上面也可以觸發(fā)load事件。
<img src="smile.gif" onload="alert('Image loaded.')">
這樣當圖像加載完畢后會顯示一個警告框。同樣的,也可以使用JavaScript。
var img = document.getElementById("myImage");
EventUtil.addHandler(img,"load",function(event) {
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
})
這里,使用JavaScript指定了onload事件處理程序。同時也傳入了event對象,盡管它也不包含什么有用的信息。不過,事件的目標是<img>元素,因此可以通過src屬性訪問并顯示該信息。
在創(chuàng)建新的<img>元素時,可以為其指定一個事件處理程序,以便圖像加載完畢后給出提示。此時,最重要的是要在指定src屬性之前指定事件。
EventUtil.addHandler(window,"load",function() {
var image = document.createElement("img");
EventUtil.addHandler(image,"load",function(event) {
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
});
document.body.appendChild(image);
image.src = "smile.gif";
});
首先為window指定了onload事件處理程序,原因在于,向DOM中添加一個新元素,所以確保頁面已經(jīng)加載完畢——如果在頁面加載前操作document.body會導致錯誤。然后,創(chuàng)建一個新的圖像元素,并設置了其onload事件處理程序。最后又將這個圖像添加到頁面中,還設置了它的src屬性。這里有一點需要注意:新圖像元素不一定要從添加到文檔后才開始下載,只要設置了src屬性就會開始下載。
還有一些元素也以非保準的方式支持load事件。在IE9+,F(xiàn)irefox,opera,chrome,safari3+及更高版本中,<script>元素也支持load事件。以便開發(fā)者確定動態(tài)加載的JavaScript文件是否加載完畢。只有在設置了<script>元素的src屬性并將該元素添加到文檔后,才會開始下載JavaScript文件,這和img元素不同。換句話說,對于<script>元素而言,指定src屬性和指定事件處理程序的先后順序不重要了。
EventUtil.addHandler(window,"load",function() {
var script = document.createElement("script");
EventUtil.addHandler(script,"load",function(event) {
alert("Loaded");
});
script.src = "example.js";
document.body.appendChild(script);
});
unload事件
與load事件對應的是unload事件,這個事件在文檔被完全卸載后觸發(fā)。只要用戶從一個頁面切換到另一個頁面,就會發(fā)生unload事件,而利用這個事件最后情況是清除引用,比避免內(nèi)存泄漏。
EventUtil.addHandler(window,"unload",function(event) {
alert("Unloaded");
});
此時生成的event對象在兼容DOM的瀏覽器中只包含target屬性(值為document)。ie8及之前版本則為這個事件對象提供了srcElement屬性。
指定事件處理程序的第二種方式,也是為body元素添加一個特性。
<body onunload="alert('Unloaded!')">
無論使用哪種方式,都要小心編寫onunload事件處理程序中的代碼。既然unload事件是在一切都被卸載后觸發(fā),那么頁面加載后存在的某些對象此時就不一定存在了,此時,操作DOM節(jié)點或者元素的樣式就會導致錯誤。
resize事件
當瀏覽器窗口被調(diào)整到一個新的高度或寬度時,就會觸發(fā)resize事件。這個事件在window上面觸發(fā),因此可以通過JavaScript或者body元素中的onresize特性來指定事件處理程序。不過,還是推薦JavaScript方式。
EventUtil.addHandler(window,"resize",function(event) {
alert("Resized");
});
關于何時觸發(fā)resize事件,不同瀏覽器有不同機制。IE,Safari,Chrome,Opera會在瀏覽器變化了1像素時就觸發(fā)resize事件,然后隨著變量不斷重復觸發(fā)。Firefox只會在用戶停止調(diào)整窗口大小時觸發(fā)resize事件。由于存在差異,注意不要在這個事件處理程序中加入大計算量代碼,因為這些代碼有可能被頻繁執(zhí)行,從而導致瀏覽器反應明顯變慢。
瀏覽器窗口最小化或最大化也會觸發(fā)resize事件
scroll事件
雖然scroll事件是在window對象上發(fā)生的,但他實際表示的是頁面中相應元素的變化。在混雜模式下,可以通過<body>元素的scrollLeft和scrollTop來監(jiān)控這一變化。
EventUtil.addHandler(window,"scroll",function(event) {
if (document.compatMode == "CSS1Compat") {
alert(document.documentElement.scrollTop);
} else {
alert(document.body.scrollTop);
}
});
以上代碼指定的事件處理程序會輸出頁面的垂直滾動位置——根據(jù)呈現(xiàn)模式不同使用了不同的元素。
與resize事件類似,scroll事件也會在文檔被滾動期間重復被觸發(fā),盡量保持事件處理程序代碼簡單。
焦點事件
焦點事件會在頁面元素獲得或失去焦點時觸發(fā)。利用這些事件并與document.hasFocus()方法及document.activeElement屬性配合,可以知曉用戶在頁面上的行蹤。