事件流
JavaScript與HTML之間的交互是通過事件實(shí)現(xiàn)的窿克。事件骏庸,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特定的交互瞬間毛甲。可以使用偵聽器(或處理程序)來預(yù)定事件具被,以便事件發(fā)生時(shí)執(zhí)行相應(yīng)的代碼玻募。這種在傳統(tǒng)軟件工程中被成為觀察員模式的模型,支持頁(yè)面的行為(js代碼)與頁(yè)面的外觀(HTML和CSS代碼)之間的松散耦合一姿。
事件最早是在IE3和Netscape Navigator2(以下簡(jiǎn)稱網(wǎng)景)中出現(xiàn)的补箍,當(dāng)時(shí)作為分擔(dān)服務(wù)器運(yùn)算負(fù)載的一種手段。在IE4和網(wǎng)景4發(fā)布時(shí)啸蜜,這兩種瀏覽器都提供了相似不相同的API,這些API并存經(jīng)過了好幾個(gè)主要版本辈挂,DOM2級(jí)規(guī)范開始嘗試以一種符合邏輯的方式來標(biāo)準(zhǔn)化DOM事件衬横。IE9、Firefox终蒂、Opera蜂林、Safari和Chrome全部已實(shí)現(xiàn)了DOM2級(jí)事件模塊的核心部分。IE8是最后一個(gè)仍然使用其專有事件系統(tǒng)的主要瀏覽器拇泣。
定義與由來
當(dāng)瀏覽器發(fā)展到第四代時(shí)噪叙,瀏覽器開發(fā)團(tuán)隊(duì)遇到了一個(gè)很有意思的問題:頁(yè)面的哪一部分會(huì)擁有某個(gè)特定的事件?想象一下霉翔,在一張紙上畫一個(gè)同心圓睁蕾,如果你把手指放在圓心上,那么你的手指指向的不是一個(gè)圓债朵,而是紙上所有的圓子眶。兩家公司的瀏覽器開發(fā)團(tuán)隊(duì)看待瀏覽器事件方面還是一致的。如果你單擊了某個(gè)按鈕序芦,他們都認(rèn)為單擊事件不僅僅發(fā)生在按鈕上臭杰。換句話說,在單擊按鈕的同時(shí)谚中,你也單擊了按鈕的容器元素渴杆,甚至也單擊了整個(gè)頁(yè)面。
事件流描述的是從頁(yè)面中接受事件的順序宪塔。但有意思的是磁奖,IE和網(wǎng)景開發(fā)團(tuán)隊(duì)居然提出了差不多完全相反的事件流的概念。IE的事件流是事件冒泡某筐,而網(wǎng)景的事件流是事件捕獲流点寥。
事件冒泡
IE的事件流叫做事件冒泡(event bubbling),即事件開始時(shí)由最具體的元素(文檔中嵌套層次最深的那個(gè)節(jié)點(diǎn))接收来吩,然后逐級(jí)向上傳播到較為不具體的節(jié)點(diǎn)(文檔)敢辩。以下面的HTML頁(yè)面為例:
<!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>
如果單擊了頁(yè)面中的<div>
元素蔽莱,那么這個(gè)click事件會(huì)按照如下順序傳播:
- <div>
- <body>
- <html>
- document
也就是說,click事件首先在<div>元素上發(fā)生戚长,而這個(gè)元素就是我們單擊的元素盗冷。然后,click事件沿DOM數(shù)向上傳播同廉,在每一級(jí)節(jié)點(diǎn)上都會(huì)發(fā)生仪糖,直至傳播到document對(duì)象。
所有瀏覽器都支持事件冒泡迫肖,但在具體實(shí)現(xiàn)上還是有一些差別锅劝。IE5.5及更早版本會(huì)跳過<html>元素直接到document對(duì)象。IE9蟆湖、Chrome故爵、Safari則將事件一直冒泡到window對(duì)象。
事件捕獲
網(wǎng)景團(tuán)隊(duì)提出的另一種事件流叫做事件捕獲(event capturing)隅津。事件捕獲的思想是不太具體的節(jié)點(diǎn)應(yīng)該更早接收到事件诬垂,而最具體的節(jié)點(diǎn)應(yīng)該最后接收到事件。事件捕獲的用意在于在事件到達(dá)預(yù)定目標(biāo)之前捕獲它伦仍。如果仍以之前HTML代碼演示捕獲例子结窘,那么單擊click元素就會(huì)以下列順序觸發(fā)click事件:
- document
- <html>
- <body>
- <div>
在事件捕獲過程中,document對(duì)象首先接收到click事件充蓝,然后事件沿DOM數(shù)依次向下隧枫,一直傳播到事件具體目標(biāo),即<div>元素谓苟。
雖然事件捕獲是網(wǎng)景唯一支持的事件流模型悠垛,但I(xiàn)E9、Safari娜谊、Chrome确买、Opera和Firefox目前也都支持這種事件流模型。盡管”DOM2級(jí)事件“規(guī)范要求事件應(yīng)該從document對(duì)象開始傳播纱皆,但這些瀏覽器都是從window對(duì)象開始捕獲事件的湾趾。
由于老版本瀏覽器不支持,很少有人使用事件捕獲派草,一般都使用事件冒泡搀缠。
DOM事件流
DOM2級(jí)事件規(guī)定的事件流包括三個(gè)階段:事件捕獲階段、處于目標(biāo)階段近迁、事件冒泡階段艺普。首先發(fā)生的是事件捕獲,為截獲事件提供了機(jī)會(huì)。然后是實(shí)際的目標(biāo)接收到事件歧譬。最后一個(gè)階段是冒泡階段岸浑,可以在這個(gè)階段對(duì)事件做出響應(yīng)。以前面簡(jiǎn)單的HTML頁(yè)面為例瑰步,單擊<div>元素會(huì)按照下圖所示順序觸發(fā)事件矢洲。
在DOM事件流中,實(shí)際的目標(biāo)<div>元素在捕獲階段不會(huì)接收到事件缩焦。這意味在捕獲階段读虏,事件從document到<html>再到<body>后就停止了。下一個(gè)階段是”處于目標(biāo)“階段袁滥,于是事件在<div>上發(fā)生盖桥,并在事件處理中被看成冒泡階段的一部分星岗。然后芙委,冒泡階段發(fā)生妖爷,事件又傳播回文檔悯辙。
IE8及更早版本不支持DOM事件流。
事件處理程序
事件就是用戶或?yàn)g覽器自身執(zhí)行的某種動(dòng)作穿香。諸如:click、load和mouseover,都是事件的名字猾普。而響應(yīng)某個(gè)個(gè)事件的函數(shù)叫做事件處理程序(或事件偵聽器)。
其實(shí)JS與HTML的交互就是事件處理程序執(zhí)行的結(jié)果本谜。
事件處理程序的名字以"on"開頭初家,因此click事件的事件處理程序(click事件偵聽器)就是onclick,load事件的事件處理程序(load事件偵聽器)就是onload乌助。
為事件指定處理程序的方式有好幾種(面試)
HTML事件處理程序
某個(gè)元素支持的每種事件溜在,都可以使用一個(gè)與相應(yīng)事件處理程序同名的HTML特性來指定。這個(gè)特性的值應(yīng)該是能夠執(zhí)行的JavaScript代碼他托。如:
<input type="button" value="Click Me" onclick="alert('Clicked')">
當(dāng)單擊這個(gè)按鈕時(shí)掖肋,就會(huì)顯示一個(gè)警告框。這個(gè)操作是通過指定onclick特性一些JavaScript代碼作為它的值來定義的赏参。由于這個(gè)值是JavaScript志笼,因此不能在其中使用未經(jīng)轉(zhuǎn)義的HTML語法字符。為了避免使用HTML實(shí)體把篓,這里使用單引號(hào)纫溃,如果想要使用雙引號(hào),可以這樣改寫:
<input type="button" value="Click Me" onclick="alert("Clicked")">
在HTML中定義的事件處理程序可以包含要執(zhí)行的具體動(dòng)作韧掩,也可以調(diào)用在頁(yè)面其他地方定義的腳本:
<!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ù)紊浩,這個(gè)函數(shù)是在一個(gè)獨(dú)立<script>元素中定義的,當(dāng)然也可以被包含在一個(gè)外部文件中。事件處理程序中的代碼在執(zhí)行時(shí)坊谁,有權(quán)訪問全局作用域中任何代碼费彼。
這樣指定事件處理程序具有一些獨(dú)到之處。首先呜袁,這樣會(huì)創(chuàng)建一個(gè)封裝著元素屬性值的函數(shù)敌买。這個(gè)函數(shù)中有一個(gè)局部變量event,也就是事件對(duì)象:
<input type="button" value="Click Me" onclick="console.log(event.type)">
<!-- 輸出click -->
event為事件對(duì)象阶界,我們打印出來看看里面有啥
<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變量虹钮,可以直接訪問事件對(duì)象,你不用自己定義它膘融,也不用從函數(shù)的參數(shù)列表中讀取芙粱。在這個(gè)函數(shù)內(nèi)部,this值等于事件的目標(biāo)元素氧映。如:
<input type="button" value="Click Me" onclick="console.log(this.value)">
<!-- 輸出 "Click Me"-->
關(guān)于這個(gè)動(dòng)態(tài)創(chuàng)建的函數(shù)春畔,另一個(gè)有意思的地方是它擴(kuò)展作用域的方式,在這個(gè)函數(shù)內(nèi)部岛都,可以像訪問局部變量一樣訪問document及該元素本身的成員律姨。這個(gè)函數(shù)使用with像下面這樣擴(kuò)展作用域:
function() {
with(document) {
with(this) {
// 元素屬性值
}
}
}
這樣一來,事件處理程序要訪問自己的屬性就容易多了臼疫。
<input type="button" value="Click Me" onclick="alert(value)">
<!-- 輸出 "Click Me" -->
總結(jié)(面試):
這種在HTML中指定事件處理程序有兩個(gè)缺點(diǎn)择份。
時(shí)差問題
用戶可能會(huì)在HTML元素一出現(xiàn)在頁(yè)面上就觸發(fā)相應(yīng)的事件,但當(dāng)時(shí)的事件處理程序有可能尚不具備執(zhí)行條件烫堤。
比如荣赶,將click事件處理函數(shù)放在HTML下方,頁(yè)面最底部鸽斟,用戶在函數(shù)解析完成前激活了click事件拔创,就會(huì)引發(fā)錯(cuò)誤。為此富蓄,很多HTML事件處理程序都會(huì)被封裝在一個(gè)try-catch塊中剩燥,以便錯(cuò)誤不會(huì)浮出水面,如:
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">
這樣立倍,如果在showMessage()函數(shù)有定義之前單擊按鈕躏吊,用戶不會(huì)看到JavaScript錯(cuò)誤,因?yàn)樵跒g覽器有機(jī)會(huì)處理錯(cuò)誤之前帐萎,錯(cuò)誤就被捕獲了比伏。
作用域問題
另一個(gè)缺點(diǎn)是,這樣擴(kuò)展事件處理程序的作用域鏈在不同瀏覽器導(dǎo)致不同結(jié)果疆导。不同JavaScript引擎遵循的標(biāo)識(shí)符規(guī)則略有差異赁项,很可能會(huì)在訪問非限定對(duì)象成員時(shí)出錯(cuò)。
分離原則
通過HTML指定事件處理程序最后一個(gè)缺點(diǎn)是HTML與JavaScript代碼緊密耦合。如果要更換事件處理程序悠菜,就要改動(dòng)HTML代碼和JavaScript代碼舰攒,而這正是開發(fā)人員摒棄HTML事件處理程序,轉(zhuǎn)而使用JavaScript指定事件處理程序的原因所在悔醋。
不要使用HTML指定事件處理程序
DOM0級(jí)事件處理程序
通過JavaScript指定事件處理方式的傳統(tǒng)方式摩窃,就是將一個(gè)函數(shù)賦值給一個(gè)事件處理程序?qū)傩浴_@種為事件處理程序賦值的方法是在第四代Web瀏覽器中出現(xiàn)芬骄。
- 簡(jiǎn)單
- 具有跨瀏覽器優(yōu)勢(shì)
每個(gè)元素(包括window和document)都有自己的事件處理程序?qū)傩曰福@些屬性通常全部小寫,例如onclick账阻。將這種屬性的值設(shè)置為一個(gè)函數(shù)蒂秘,就可以指定事件處理程序。
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
};
在此淘太,我們通過文檔對(duì)象取得了一個(gè)按鈕的引用姻僧,然后為它指定了onclick事件處理程序。但要注意蒲牧,在這些代碼運(yùn)行以前不會(huì)指定事件處理程序撇贺,因此如果這些代碼在頁(yè)面中位于按鈕后面,就有可能在一段時(shí)間內(nèi)怎么單擊都沒有反應(yīng)冰抢。
使用DOM0級(jí)方法指定的事件處理程序被認(rèn)為是元素的方法松嘶,因此,這時(shí)候的事件處理程序是在元素的作用域中運(yùn)行晒屎;換句話說喘蟆,程序中的this引用當(dāng)前元素缓升。
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert(this.id); // "myBtn"
};
單擊按鈕顯示的是元素的ID鼓鲁,這個(gè)ID是通過this.id取得的。不僅僅是ID港谊,實(shí)際上可以在事件處理程序中通過this訪問元素的任何屬性和方法骇吭。
以這種方式添加的事件處理程序會(huì)在事件流的冒泡階段被處理。
也可以刪除通過DOM0級(jí)方法指定的事件處理程序歧寺。
btn.onclick = null; // 刪除事件處理程序
此時(shí)單擊按鈕不會(huì)有任何動(dòng)作發(fā)生燥狰。
個(gè)人理解:沒有動(dòng)作發(fā)生因?yàn)闆]有事件處理程序,但是的確存在這個(gè)事件被觸發(fā)斜筐。只是我們沒有對(duì)這個(gè)事件發(fā)生時(shí)“做些什么”而已龙致。
如果你使用HTML指定事件處理程序,那么onclick屬性的值就是一個(gè)包含著在同名HTML特性中指定的代碼的函數(shù)顷链。而將相應(yīng)的屬性設(shè)置為null目代,也可以刪除以這種方式指定的事件e處理程序。
DOM2級(jí)事件處理程序
"DOM2級(jí)事件"定義了兩個(gè)方法,用于處理指定和刪除事件處理程序的操作:addEventListener()和removeEventListener()榛了。所有DOM節(jié)點(diǎn)中都包含這兩個(gè)方法在讶,并且它們都接受3個(gè)參數(shù):要處理的事件名、作為事件處理程序的函數(shù)和一個(gè)布爾值霜大。最后這個(gè)布爾值參數(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);
上面的代碼為一個(gè)按鈕添加了onclick事件處理程序,而且該事件會(huì)在冒泡階段被觸發(fā)(因?yàn)樽詈笠粋€(gè)參數(shù)false)湖笨。與DOM0級(jí)方法一樣旗扑,這里添加的事件處理程序也是在其依附的元素的作用域中運(yùn)行。使用DOM2級(jí)方法添加事件處理程序的主要好處是可以添加多個(gè)事件處理程序慈省。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
btn.addEventListener("click",function() {
alert("Hello World!");
}, false)
調(diào)用的事件處理程序會(huì)按照添加它們的順序觸發(fā)臀防,因此首先會(huì)顯示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);
})
這樣行嗎?當(dāng)然不行笑窜,兩個(gè)方法定義的事件處理函數(shù)根本就不是”一個(gè)東西“致燥,即它們是兩個(gè)不相干的函數(shù)對(duì)象。
我們需要使用函數(shù)表達(dá)式排截。
var btn = document.getElementById("myBtn");
var handler = function() {
alert(this.id);
};
btn.addEventListener("click",handler,false);
btn.removeEventListener("click",handler,false);
大多數(shù)情況嫌蚤,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度的兼容各種瀏覽器断傲。
不是特別需要脱吱,不建議在事件捕獲階段注冊(cè)事件處理程序。
IE事件處理程序(IE兼容方法)
IE實(shí)現(xiàn)了與DOM中類似的兩個(gè)方法:attachEvent()
和detachEvent()
這兩個(gè)方法接受相同的兩個(gè)參數(shù):事件處理程序名稱和事件處理函數(shù)认罩。由于IE8及更早版本只支持事件冒泡箱蝠,所以通過attachEvent()添加的事件處理程序都會(huì)被添加到冒泡階段。
使用attachEvent()為按鈕添加一個(gè)事件處理程序:
btn.attachEvent("onclick",function() {
alert("Clicked");
});
注意垦垂,參數(shù)從"click"變?yōu)榱?onclick"宦搬。
IE中使用attachEvent()與使用DOM0級(jí)方法的主要區(qū)別在于事件處理程序的作用域。在使用DOM0級(jí)方法的情況下劫拗,事件處理程序會(huì)在其所屬元素的作用域內(nèi)運(yùn)行间校;在使用attachEvent()方法的情況下,事件處理程序會(huì)在全局作用域下與西寧页慷,因此this等于window憔足。
btn.attachEvent("onclick",function() {
alert(this === window);
});
在編寫跨瀏覽器的代碼時(shí)聂渊,記住。
與addEventListener()類似四瘫,attachEvent()方法也可以用來為一個(gè)元素添加多個(gè)事件處理程序汉嗽。
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;
}
}
}
這連個(gè)方法都會(huì)檢測(cè)傳入元素是否存在DOM2級(jí)方法弓叛。如果存在,使用該方法:傳入事件類型诚纸、事件處理程序函數(shù)和第三個(gè)參數(shù)false(表示冒泡階段)撰筷。如果存在的是IE的方法,則采取第二種方案畦徘。
為了在IE8級(jí)更早版本中運(yùn)行毕籽,此時(shí)的事件類型必須加上"on"前綴。
最后一種可能是使用DOM0級(jí)方法(在現(xiàn)在瀏覽器中井辆,不會(huì)執(zhí)行這里的代碼)关筒。此時(shí),我們使用的是方括號(hào)語法來講屬性名指定為事件處理程序杯缺,或者將屬性設(shè)置為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級(jí)對(duì)每個(gè)事件只支持一個(gè)事件處理程序包雀。不過現(xiàn)在只支持DOM0級(jí)的瀏覽器應(yīng)該很少了。
事件對(duì)象
在觸發(fā)DOM上的事件時(shí)勿负,會(huì)產(chǎn)生一個(gè)事件對(duì)象event馏艾,這個(gè)對(duì)象中包含所有與事件有關(guān)的信息劳曹。包活導(dǎo)致事件的元素奴愉、事件的類型及其他與特定事件相關(guān)的信息。例如铁孵,鼠標(biāo)操作導(dǎo)致的事件對(duì)象中锭硼,會(huì)包含鼠標(biāo)位置的信息,鍵盤操作導(dǎo)致的事件對(duì)象中蜕劝,會(huì)包含與按下的鍵有關(guān)的信息檀头。
所有的瀏覽器都支持event對(duì)象轰异,但支持方式不同。
DOM中的事件對(duì)象
兼容DOM的瀏覽器會(huì)將一個(gè)event對(duì)象傳入到事件處理程序中暑始。無論指定事件處理程序時(shí)使用什么方法(DOM0級(jí)或DOM2級(jí))搭独,都會(huì)傳入event對(duì)象。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.type); // "click"
};
btn.addEventListener("click",function(event) {
alert(event.type); // "click<"
},false);
event屬性始終都會(huì)包含被觸發(fā)的事件類型廊镜,例如"click"牙肝。
在通過HTM特性指定事件處理程序時(shí),變量event中保存著event對(duì)象嗤朴。
<input type="button" id="myBtn" value="Click Me" onclick="alert(event.type)">
以這種方式提供event對(duì)象配椭,可以讓HTML特性事件處理程序與JavaScript函數(shù)執(zhí)行相同的操作。
event對(duì)象包含與創(chuàng)建它的特定事件有關(guān)的屬性和方法雹姊。觸發(fā)的事件類型不一樣股缸,可用的屬性和方法也不一樣。不過所有屬性都會(huì)有一些共有的成員吱雏。
/*
屬性/方法 類型 讀/寫 說明
bubbles Boolean 只讀 表明事件是否冒泡
cancelable Boolean 只讀 表明是否可以取消事件的默認(rèn)行為
currentTarget Element 只讀 其事件處理程序當(dāng)前正在處理事件的那個(gè)元素
defaultPrevented Boolean 只讀 為true表示已經(jīng)調(diào)用了preventDefault()
detail Integer 只讀 與事件有關(guān)的細(xì)節(jié)信息
eventPhase Integer 只讀 調(diào)用事件處理程序的階段:1捕獲階段敦姻、2"處于目標(biāo)"、3表示冒泡階段
preventDefault Function 只讀 取消事件的默認(rèn)行為歧杏。如果cancleable是true替劈,則可以使用這個(gè)方法
stopImmediatePropagation() Function 只讀 取消事件的進(jìn)一步捕獲或冒泡,同時(shí)阻止任何事件處理程序被調(diào)用
stopPropagation() Function 只讀 取消事件的進(jìn)一步捕獲或冒泡得滤。如果bubbles為true陨献,則可以使用這個(gè)方法
target Element 只讀 事件的目標(biāo)
trusted Boolean 只讀 為true表示目標(biāo)是瀏覽器生成的。為false表示事件是由開發(fā)人員通過JavaScript創(chuàng)建的
type String 只讀 被觸發(fā)的事件的類型
view AbstractView 只讀 與事件關(guān)聯(lián)的抽象視圖懂更。等同于發(fā)生事件的window對(duì)象眨业。
*/
在事件處理程序內(nèi)部,對(duì)象this始終等于cureentTarget的值沮协,而target則只包含事件的實(shí)際目標(biāo)龄捡。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.currentTarget === this); // true
alert(event.target === this); // true
}
這個(gè)例子檢測(cè)了currentTarget和target與this的值。由于click事件的目標(biāo)是按鈕慷暂,因此這三個(gè)值是相等的聘殖。
如果事件處理程序存在于按鈕的父節(jié)點(diǎn)中(例如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
}
當(dāng)單擊這個(gè)例子中的按鈕時(shí)奸腺,this和currentTarget都等于document.body,因此事件處理程序是注冊(cè)到這個(gè)元素上的血久。然而突照,target元素卻等于按鈕元素,因?yàn)樗莄lick事件真正的目標(biāo)(用戶操作點(diǎn)擊了按鈕)氧吐。只是按鈕上沒有事件處理程序讹蘑,沒喲函數(shù)執(zhí)行末盔。結(jié)果click事件冒泡到了body,在那里事件得到處理座慰。
在需要通過一個(gè)函數(shù)處理多個(gè)事件時(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邦尊。
當(dāng)單擊按鈕時(shí)背桐,出現(xiàn)一個(gè)警告框。當(dāng)按鈕移動(dòng)到按鈕上面時(shí)蝉揍,背景顏色變化成紅色链峭。當(dāng)鼠標(biāo)移動(dòng)出按鈕時(shí),背景顏色回復(fù)默認(rèn)值又沾。
這里通過檢測(cè)event.type屬性弊仪,讓函數(shù)能夠確定發(fā)生了什么事件,并執(zhí)行相應(yīng)操作杖刷。
要阻止特定事件的默認(rèn)行為励饵,可以使用preventDefault()方法。例如滑燃,鏈接的默認(rèn)行為就是在被單擊時(shí)導(dǎo)航到其href指定的URL役听。如果想阻止鏈接導(dǎo)航這一默認(rèn)行為,那么通過onclick事件處理程序可以取消它表窘。
var link = document.getElementById("myLink");
link.onclick = function(event) {
event.preventDefault();
};
另外只有cancelable屬性設(shè)置為true的事件典予,才可以使用preventDefault()來取消去默認(rèn)行為。
另外乐严,stopPropagation()方法用于立即停止事件在DOM層次中的傳播瘤袖,即取消進(jìn)一步的事件捕獲或冒泡。
例如昂验,直接添加到一個(gè)按鈕的事件處理程序可以調(diào)用stopPropagation()捂敌,從而避免觸發(fā)注冊(cè)在document.body上面的事件處理程序。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
alert("Body clicked");
}
事件在目標(biāo)階段被觸發(fā)后既琴,不會(huì)傳播到document.body占婉,因此就不會(huì)觸發(fā)注冊(cè)在這個(gè)元素上的onclick事件處理對(duì)象。
事件對(duì)象的eventPhase屬性呛梆,可以用來確定事件當(dāng)前位于事件流的哪個(gè)階段锐涯。
- 捕獲階段:1
- 目標(biāo)階段: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的那一個(gè)磕诊,結(jié)果會(huì)彈出一個(gè)警告框顯示表示eventPhase的1填物。接著纹腌,會(huì)觸發(fā)在按鈕上注冊(cè)的事件處理程序,此時(shí)的eventPhase值為2滞磺。最后一個(gè)被觸發(fā)的事件處理程序升薯,是在冒泡階段執(zhí)行的添加到document.body上的那一個(gè),顯示eventPhase的值為3击困。
當(dāng)eventPhase等于2時(shí)涎劈,this、target阅茶、currentTarget始終相等蛛枚。
只有事件處理程序執(zhí)行期間,event對(duì)象才會(huì)存在脸哀;一旦事件處理程序執(zhí)行完成蹦浦,event就會(huì)銷毀。
IE中的事件對(duì)象
與訪問DOM中的event對(duì)象不同撞蜂,要訪問IE中的event對(duì)象有幾種不同方式盲镶,取決與執(zhí)行事件處理程序的方法。在使用DOM0級(jí)方法添加事件處理程序時(shí)蝌诡,event對(duì)象作為window對(duì)象的一個(gè)屬性存在溉贿。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
var event = window.event;
alert(event.type); // "click"
};
在此,我們通過window.event取得了event對(duì)象浦旱,并檢測(cè)了被觸發(fā)事件的類型宇色。
如果使用attachEvent()添加的,那么就會(huì)有一個(gè)event對(duì)象作為參數(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對(duì)象同樣也包含與創(chuàng)建它的事件相關(guān)的屬性和方法代兵。其中很多屬性和方法都有對(duì)應(yīng)的或者相關(guān)的DOM屬性和方法。與DOM的event對(duì)象一樣爷狈,這些屬性和方法也會(huì)因?yàn)槭录愋偷牟煌煌灿埃惺录?duì)象都有這些:
/*
屬性/方法 類型 讀/寫 說明
cancelBubble Boolean 讀/寫 默認(rèn)值false,將其設(shè)置為true可以取消事件冒泡
returnValue Boolean 讀/寫 默認(rèn)值為true涎永,將其設(shè)置為false可以取消事件的默認(rèn)行為
srcElement Element 只讀 事件的目標(biāo)
type String 只讀 被觸發(fā)的事件的類型
/*
因?yàn)槭录幚沓绦虻淖饔糜蚴歉鶕?jù)它的方式來確定的思币,所以不能認(rèn)為this會(huì)始終等于事件目標(biāo)。故而羡微,最好還是使用event.srcElement比較保險(xiǎn)谷饿。
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
});
在第一個(gè)事件處理程序中(使用DOM0級(jí)方法),srcElement屬性等于this妈倔,但在第二個(gè)事件處理程序中博投,這兩者的值不同。
returnValue屬性相當(dāng)于DOM中的preventDefault()方法盯蝴,它們的作用都是取消給定事件的默認(rèn)行為毅哗,只要將returnValue設(shè)置為false听怕,就可以阻止默認(rèn)行為。
var link = document.getElementById("myLink");
link.onclick = function() {
window.event.returnValue = false;
};
相應(yīng)的虑绵,cancelBubbles屬性與DOM中的stopPropagation()方法相同尿瞭,都是用來停止事件冒泡的。由于IE不支持事件捕獲翅睛,因?yàn)橹荒苋∠录芭萆椋坏玸topPropagation()可以同時(shí)取消事件捕獲和冒泡。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function() {
alert("Body clicked");
};
跨瀏覽器事件對(duì)象
雖然DOM和IE中的event對(duì)象不同捕发,但基于它們之間的相似性依舊可以拿出跨瀏覽器的方案來疏旨。
IE中event對(duì)象的全部信息和方法DOM對(duì)象中都有,只不過實(shí)現(xiàn)方式不一樣扎酷。
var EventUtil = {
addHandler: function (element, type, handler) { // 跨瀏覽器添加事件處理程序
// 省略
},
getEvent: function (event) { // 跨瀏覽器得到事件對(duì)象
return event ? event : window.event;
},
getTarget: function (event) { // 跨瀏覽器得到事件目標(biāo)
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) { // 跨瀏覽器組織默認(rèn)行為
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
事件類型
Web瀏覽器中可能發(fā)生的事件有很多類型充石。不同的事件類型具有不同的信息,而”DOM3及事件“規(guī)定了以下幾類事件霞玄。
- UI(User Interface)事件骤铃,當(dāng)用戶與頁(yè)面上的元素交互時(shí)觸發(fā)榨呆;
- 焦點(diǎn)事件绿聘,當(dāng)元素獲得或失去焦點(diǎn)時(shí)觸發(fā);
- 鼠標(biāo)事件辜王,當(dāng)用戶通過鼠標(biāo)在頁(yè)面上執(zhí)行操作時(shí)觸發(fā)惫企;
- 滾輪事件撕瞧,當(dāng)使用鼠標(biāo)(或類似設(shè)備)時(shí)觸發(fā);
- 文本事件狞尔,當(dāng)在文檔中輸入文本時(shí)觸發(fā)丛版;
- 鍵盤事件,當(dāng)用戶通過鍵盤在頁(yè)面上執(zhí)行操作時(shí)觸發(fā)偏序;
- 合成事件页畦,當(dāng)為IME(Input Method Editor 輸入法編輯器)輸入字符時(shí)觸發(fā);
- 變動(dòng)(mutation)事件研儒,當(dāng)?shù)讓覦OM結(jié)構(gòu)發(fā)生變化時(shí)觸發(fā)豫缨。
除了這幾類事件之外,HTML5也定義了一組事件端朵,而有些瀏覽器還會(huì)在DOM和BOM中實(shí)現(xiàn)其他專有事件好芭。這些專有的事件一般都是根據(jù)開發(fā)者需求定制的。
UI事件
UI事件指的是那些不一定與用戶操作有關(guān)的事件冲呢。這些事件在DOM規(guī)范出現(xiàn)之前舍败,都是以這種或那種形式存在的,而在DOM規(guī)范中保留是為了向后兼容。現(xiàn)有的UI事件如下邻薯。
- DOMActivate:表示元素已經(jīng)被用戶操作(通過鼠標(biāo)或鍵盤)激活裙戏,這個(gè)事件在DOM3級(jí)事件中被廢棄。
- load:當(dāng)頁(yè)面完全加載后在window上面觸發(fā)弛说,當(dāng)所有框架都加載完畢時(shí)在框架集上面觸發(fā)挽懦,當(dāng)圖像加載完畢時(shí)在<Img>元素上面觸發(fā)翰意,或者當(dāng)嵌入的內(nèi)容加載完畢時(shí)在<object>元素上面觸發(fā)木人。
- unload:當(dāng)頁(yè)面完全卸載后在window上面觸發(fā),當(dāng)所有框架都卸載后在框架集上面觸發(fā)冀偶,或者當(dāng)嵌入的內(nèi)容卸載完畢后<object>元素上面觸發(fā)醒第。
- abort:在用戶停止下載過程時(shí),如果嵌入的內(nèi)容沒有加載完进鸠,則在<object>元素上面觸發(fā)稠曼。
- error:當(dāng)發(fā)生JavaScript錯(cuò)誤時(shí)在window上面觸發(fā),當(dāng)無法加載圖像時(shí)在<img>元素上面觸發(fā)客年,無法加載嵌入內(nèi)容時(shí)<object>上觸發(fā)霞幅,或者當(dāng)一或多個(gè)框架無法加載時(shí)在框架集上觸發(fā)。
- select:當(dāng)用戶選擇<input>或<textarea>中一或多個(gè)字符時(shí)被觸發(fā)量瓜。
- resize:當(dāng)窗口或框架的大小變化時(shí)在window或框架上面觸發(fā)司恳。
- scroll:當(dāng)用戶滾動(dòng)帶滾動(dòng)條的元素中的內(nèi)容時(shí),在該元素上面觸發(fā)绍傲。<body>元素中包含所加載頁(yè)面的滾動(dòng)條扔傅。
多數(shù)這些事件斗魚window對(duì)象或表單控件相關(guān)。
load事件
JavaScript中最常用 的一個(gè)事件就是load烫饼。當(dāng)頁(yè)面完全加載后(包括所有圖像猎塞、JavaScript文件、CSS文件等外部資源)杠纵,就會(huì)觸發(fā)window上面的load事件荠耽。有兩種定義onload事件處理程序的方式。
EventUtil.addHandler(window,"load",function(event) {
alert("Loaded!");
});
這是通過JavaScript來指定事件處理程序的方式比藻,使用了之前跨瀏覽器的EventUtil對(duì)象骇塘。同樣的,這里也給事件傳入了一個(gè)event對(duì)象韩容。這個(gè)event對(duì)象中不包括有關(guān)這個(gè)事件的任何附加信息款违,但在兼容DOM瀏覽器中,event.target屬性的值會(huì)被設(shè)置為document群凶,而IE并不會(huì)為這個(gè)事件設(shè)置srcElement屬性插爹。
第二種指定onload事件處理程序的方式是為body元素添加一個(gè)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元素中通過相應(yīng)的特性來指定赠尾,因?yàn)樵贖TML中無法訪問window元素力穗。實(shí)際上,這只是為了保證向后兼容的一種權(quán)宜之計(jì)气嫁。建議使用JavaScript方式当窗。
圖像上面也可以觸發(fā)load事件。
<img src="smile.gif" onload="alert('Image loaded.')">
這樣當(dāng)圖像加載完畢后會(huì)顯示一個(gè)警告框寸宵。同樣的崖面,也可以使用JavaScript。
var img = document.getElementById("myImage");
EventUtil.addHandler(img,"load",function(event) {
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
})
這里梯影,使用JavaScript指定了onload事件處理程序巫员。同時(shí)也傳入了event對(duì)象,盡管它也不包含什么有用的信息甲棍。不過简识,事件的目標(biāo)是<img>元素,因此可以通過src屬性訪問并顯示該信息感猛。
在創(chuàng)建新的<img>元素時(shí)七扰,可以為其指定一個(gè)事件處理程序,以便圖像加載完畢后給出提示陪白。此時(shí)颈走,最重要的是要在指定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中添加一個(gè)新元素,所以確保頁(yè)面已經(jīng)加載完畢——如果在頁(yè)面加載前操作document.body會(huì)導(dǎo)致錯(cuò)誤司致。然后拆吆,創(chuàng)建一個(gè)新的圖像元素,并設(shè)置了其onload事件處理程序脂矫。最后又將這個(gè)圖像添加到頁(yè)面中枣耀,還設(shè)置了它的src屬性。這里有一點(diǎn)需要注意:新圖像元素不一定要從添加到文檔后才開始下載庭再,只要設(shè)置了src屬性就會(huì)開始下載捞奕。
還有一些元素也以非保準(zhǔn)的方式支持load事件。在IE9+拄轻,F(xiàn)irefox颅围,opera,chrome恨搓,safari3+及更高版本中院促,<script>元素也支持load事件筏养。以便開發(fā)者確定動(dòng)態(tài)加載的JavaScript文件是否加載完畢。只有在設(shè)置了<script>元素的src屬性并將該元素添加到文檔后常拓,才會(huì)開始下載JavaScript文件渐溶,這和img元素不同。換句話說弄抬,對(duì)于<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事件對(duì)應(yīng)的是unload事件掂恕,這個(gè)事件在文檔被完全卸載后觸發(fā)拖陆。只要用戶從一個(gè)頁(yè)面切換到另一個(gè)頁(yè)面,就會(huì)發(fā)生unload事件竹海,而利用這個(gè)事件最后情況是清除引用慕蔚,比避免內(nèi)存泄漏丐黄。
EventUtil.addHandler(window,"unload",function(event) {
alert("Unloaded");
});
此時(shí)生成的event對(duì)象在兼容DOM的瀏覽器中只包含target屬性(值為document)斋配。ie8及之前版本則為這個(gè)事件對(duì)象提供了srcElement屬性。
指定事件處理程序的第二種方式灌闺,也是為body元素添加一個(gè)特性艰争。
<body onunload="alert('Unloaded!')">
無論使用哪種方式,都要小心編寫onunload事件處理程序中的代碼桂对。既然unload事件是在一切都被卸載后觸發(fā)甩卓,那么頁(yè)面加載后存在的某些對(duì)象此時(shí)就不一定存在了,此時(shí)蕉斜,操作DOM節(jié)點(diǎn)或者元素的樣式就會(huì)導(dǎo)致錯(cuò)誤逾柿。
resize事件
當(dāng)瀏覽器窗口被調(diào)整到一個(gè)新的高度或?qū)挾葧r(shí),就會(huì)觸發(fā)resize事件宅此。這個(gè)事件在window上面觸發(fā)机错,因此可以通過JavaScript或者body元素中的onresize特性來指定事件處理程序。不過父腕,還是推薦JavaScript方式弱匪。
EventUtil.addHandler(window,"resize",function(event) {
alert("Resized");
});
關(guān)于何時(shí)觸發(fā)resize事件,不同瀏覽器有不同機(jī)制璧亮。IE萧诫,Safari,Chrome枝嘶,Opera會(huì)在瀏覽器變化了1像素時(shí)就觸發(fā)resize事件帘饶,然后隨著變量不斷重復(fù)觸發(fā)。Firefox只會(huì)在用戶停止調(diào)整窗口大小時(shí)觸發(fā)resize事件群扶。由于存在差異及刻,注意不要在這個(gè)事件處理程序中加入大計(jì)算量代碼,因?yàn)檫@些代碼有可能被頻繁執(zhí)行,從而導(dǎo)致瀏覽器反應(yīng)明顯變慢提茁。
瀏覽器窗口最小化或最大化也會(huì)觸發(fā)resize事件
scroll事件
雖然scroll事件是在window對(duì)象上發(fā)生的淹禾,但他實(shí)際表示的是頁(yè)面中相應(yīng)元素的變化。在混雜模式下茴扁,可以通過<body>元素的scrollLeft和scrollTop來監(jiān)控這一變化铃岔。
EventUtil.addHandler(window,"scroll",function(event) {
if (document.compatMode == "CSS1Compat") {
alert(document.documentElement.scrollTop);
} else {
alert(document.body.scrollTop);
}
});
以上代碼指定的事件處理程序會(huì)輸出頁(yè)面的垂直滾動(dòng)位置——根據(jù)呈現(xiàn)模式不同使用了不同的元素。
與resize事件類似峭火,scroll事件也會(huì)在文檔被滾動(dòng)期間重復(fù)被觸發(fā)毁习,盡量保持事件處理程序代碼簡(jiǎn)單。
焦點(diǎn)事件
焦點(diǎn)事件會(huì)在頁(yè)面元素獲得或失去焦點(diǎn)時(shí)觸發(fā)卖丸。利用這些事件并與document.hasFocus()方法及document.activeElement屬性配合纺且,可以知曉用戶在頁(yè)面上的行蹤。