十二、事件

??JavaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的抢韭。

??事件薪贫,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特定的交互瞬間。

??可以使用偵聽器(或處理程序)來(lái)預(yù)定事件刻恭,以便事件發(fā)生時(shí)執(zhí)行相應(yīng)的代碼瞧省。

??這種在傳統(tǒng)軟件工程中被稱為觀察員模式的模型,支持頁(yè)面的行為(JavaScript 代碼)與頁(yè)面的外觀(HTML 和 CSS 代碼)之間的松散耦合鳍贾。

??瀏覽器的事件系統(tǒng)相對(duì)比較復(fù)雜鞍匾。盡管所有主要瀏覽器已經(jīng)實(shí)現(xiàn)了 “DOM 2 級(jí)事件”,但這個(gè)規(guī)范本身并沒有涵蓋所有事件類型骑科。

??瀏覽器對(duì)象模型(BOM)也支持一些事件橡淑,這些事件與文檔對(duì)象模型(DOM)事件之間的關(guān)系并不十分清晰,因?yàn)?BOM 事件長(zhǎng)期沒有規(guī)范可以遵循(HTML5 后來(lái)給出了詳細(xì)的說明)咆爽。

??隨著 DOM3 級(jí)的出現(xiàn)梁棠,增強(qiáng)后的 DOM 事件 API 變得更加繁瑣。使用事件有時(shí)相對(duì)簡(jiǎn)單伍掀,有時(shí)則非常復(fù)雜掰茶,難易程度會(huì)因你的需求而不同。不過蜜笤,有關(guān)事件的一些核心概念是一定要理解的濒蒋。

1、事件流

??當(dāng)瀏覽器發(fā)展到第四代時(shí)(IE4 及 Netscape Communicator 4),瀏覽器開發(fā)團(tuán)隊(duì)遇到了一個(gè)很有意思的問題:頁(yè)面的哪一部分會(huì)擁有某個(gè)特定的事件沪伙?要明白這個(gè)問題問的是什么瓮顽,可以想象畫在一張紙上的一組同心圓。如果你把手指放在圓心上围橡,那么你的手指指向的不是一個(gè)圓暖混,而是紙上的所有圓。

??兩家公司的瀏覽器開發(fā)團(tuán)隊(duì)在看待瀏覽器事件方面還是一致的翁授。如果你單擊了某個(gè)按鈕拣播,他們都認(rèn)為單擊事件不僅僅發(fā)生在按鈕上。換句話說收擦,在單擊按鈕的同時(shí)贮配,你也單擊了按鈕的容器元素,甚至也單擊了整個(gè)頁(yè)面塞赂。

??事件流描述的是從頁(yè)面中接收事件的順序泪勒。但有意思的是,IE 和 Netscape 開發(fā)團(tuán)隊(duì)居然提出了差不多是完全相反的事件流的概念宴猾。
??IE 的事件流是事件冒泡流圆存,而 Netscape Communicator 的事件流是事件捕獲流。

1.1仇哆、 事件冒泡

??IE 的事件流叫做事件冒泡(event bubbling)沦辙,即事件開始時(shí)由最具體的元素(文檔中嵌套層次最深的那個(gè)節(jié)點(diǎn))接收,然后逐級(jí)向上傳播到較為不具體的節(jié)點(diǎn)(文檔)讹剔。以下面的 HTML 頁(yè)面為例:

<!DOCTYPE html>
<html>
<head>
    <title>Event Bubbling Example</title>
</head>
<body>
    <div id="myDiv">Click Me</div>
</body>
</html>

??如果你單擊了頁(yè)面中的<div>元素怕轿,那么這個(gè) click 事件會(huì)按照如下順序傳播:
????(1) <div>
????(2) <body>
????(3) <html>
????(4) document

??也就是說,click 事件首先在<div>元素上發(fā)生辟拷,而這個(gè)元素就是我們單擊的元素撞羽。然后,click 事件沿 DOM 樹向上傳播衫冻,在每一級(jí)節(jié)點(diǎn)上都會(huì)發(fā)生诀紊,直至傳播到 document 對(duì)象。下圖展示了事件冒泡的過程隅俘。

??所有現(xiàn)代瀏覽器都支持事件冒泡邻奠,但在具體實(shí)現(xiàn)上還是有一些差別贺氓。

??IE5.5 及更早版本中的事件冒泡會(huì)跳過<html>元素(從<body>直接跳到 document)罗洗。IE9、Firefox籍滴、Chrome 和 Safari 則將事件一直冒泡到 window 對(duì)象蒙畴。

1.2贰镣、 事件捕獲

??Netscape Communicator 團(tuán)隊(duì)提出的另一種事件流叫做事件捕獲(event capturing)呜象。
??事件捕獲的思想是不太具體的節(jié)點(diǎn)應(yīng)該更早接收到事件,而最具體的節(jié)點(diǎn)應(yīng)該最后接收到事件碑隆。
??事件捕獲的用意在于在事件到達(dá)預(yù)定目標(biāo)之前捕獲它恭陡。

??如果仍以前面的 HTML 頁(yè)面作為演示事件捕獲的例子,那么單擊<div>元素就會(huì)以下列順序觸發(fā) click 事件上煤。
??(1) document
??(2) <html>
??(3) <body>
??(4) <div>

??在事件捕獲過程中休玩,document 對(duì)象首先接收到 click 事件,然后事件沿 DOM 樹依次向下劫狠,一直傳播到事件的實(shí)際目標(biāo)拴疤,即<div>元素。下圖展示了事件捕獲的過程独泞。

??雖然事件捕獲是 Netscape Communicator 唯一支持的事件流模型遥赚,但 IE9、Safari阐肤、Chrome、Opera 和 Firefox 目前也都支持這種事件流模型讲坎。

??盡管“DOM2 級(jí)事件”規(guī)范要求事件應(yīng)該從 document 對(duì)象
開始傳播孕惜,但這些瀏覽器都是從 window 對(duì)象開始捕獲事件的。

??由于老版本的瀏覽器不支持晨炕,因此很少有人使用事件捕獲衫画。我們也建議讀者放心地使用事件冒泡,在有特殊需要時(shí)再使用事件捕獲瓮栗。

1.3削罩、 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ā)生宝与,并在事件處理(后面將會(huì)討論這個(gè)概念)中被看成冒泡階段的一部分。

??然后,冒泡階段發(fā)生伴鳖,事件又傳播回文檔节值。

??多數(shù)支持 DOM 事件流的瀏覽器都實(shí)現(xiàn)了一種特定的行為;即使“DOM2 級(jí)事件”規(guī)范明確要求捕獲階段不會(huì)涉及事件目標(biāo)榜聂,但 IE9搞疗、Safari、Chrome须肆、Firefox 和 Opera 9.5 及更高版本都會(huì)在捕獲階段觸發(fā)事件對(duì)象上的事件匿乃。結(jié)果,就是有兩個(gè)機(jī)會(huì)在目標(biāo)對(duì)象上面操作事件豌汇。

??IE9幢炸、Opera、Firefox拒贱、Chrome 和 Safari 都支持 DOM 事件流宛徊;IE8 及更早版本不支持 DOM 事件流。

2逻澳、 事件處理程序

??事件就是用戶或?yàn)g覽器自身執(zhí)行的某種動(dòng)作闸天。諸如 click、load 和 mouseover斜做,都是事件的名字苞氮。

??而響應(yīng)某個(gè)事件的函數(shù)就叫做事件處理程序(或事件偵聽器)。

??事件處理程序的名字以"on"開頭瓤逼,因此 click 事件的事件處理程序就是 onclick笼吟,load 事件的事件處理程序就是 onload。為事件指定處理程序的方式有好幾種霸旗。

2.1贷帮、 HTML 事件處理程序

??某個(gè)元素支持的每種事件,都可以使用一個(gè)與相應(yīng)事件處理程序同名的 HTML 特性來(lái)指定诱告。
??這個(gè)特性的值應(yīng)該是能夠執(zhí)行的 JavaScript 代碼皿桑。例如,要在按鈕被單擊時(shí)執(zhí)行一些 JavaScript蔬啡,可以像下面這樣編寫代碼:

<input type="button" value="Click Me" onclick="alert('Clicked')" />

??當(dāng)單擊這個(gè)按鈕時(shí)诲侮,就會(huì)顯示一個(gè)警告框。這個(gè)操作是通過指定 onclick 特性并將一些 JavaScript 代碼作為它的值來(lái)定義的箱蟆。
??由于這個(gè)值是 JavaScript沟绪,因此不能在其中使用未經(jīng)轉(zhuǎn)義的 HTML 語(yǔ)法字符,例如和號(hào)(&)空猜、雙引號(hào)("")绽慈、小于號(hào)(<)或大于號(hào)(>)恨旱。
??為了避免使用 HTML 實(shí)體,這里使用了單引號(hào)坝疼。如果想要使用雙引號(hào)搜贤,那么就要將代碼改寫成如下所示:

<input type="button" value="Click Me" onclick="alert(&quot;Clicked&quot;)" />

??在 HTML 中定義的事件處理程序可以包含要執(zhí)行的具體動(dòng)作,也可以調(diào)用在頁(yè)面其他地方定義的腳本钝凶,如下面的例子所示:

<script type="text/javascript">
    function showMessage(){
        alert("Hello world!");
    }
</script>
<input type="button" value="Click Me" onclick="showMessage()" />

??在這個(gè)例子中仪芒,單擊按鈕就會(huì)調(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ì)象:

<!-- 輸出 "click" -->
<input type="button" value="Click Me" onclick="alert(event.type)">

??通過 event 變量,可以直接訪問事件對(duì)象隆敢,你不用自己定義它发皿,也不用從函數(shù)的參數(shù)列表中讀取。

??在這個(gè)函數(shù)內(nèi)部筑公,this 值等于事件的目標(biāo)元素,例如:

<!-- 輸出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(this.value)">

??關(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){
            // 元素屬性值
        }
    }
}

??如此一來(lái)捣作,事件處理程序要訪問自己的屬性就簡(jiǎn)單多了。下面這行代碼與前面的例子效果相同:

<!-- 輸出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(value)">

??如果當(dāng)前元素是一個(gè)表單輸入元素鹅士,則作用域中還會(huì)包含訪問表單元素(父元素)的入口券躁,這個(gè)函數(shù)就變成了如下所示:

function(){
    with(document){
        with(this.form){
            with(this){
                // 元素屬性值
            }
        }
    }
}

??實(shí)際上,這樣擴(kuò)展作用域的方式掉盅,無(wú)非就是想讓事件處理程序無(wú)需引用表單元素就能訪問其他表單字段也拜。例如:

<form method="post">
    <input type="text" name="username" value="">
    <input type="button" value="Echo Username" onclick="alert(username.value)">
</form>

??在上述例子中,單擊按鈕會(huì)顯示文本框中的文本趾痘。值得注意的是慢哈,這里直接引用了 username 元素。
??不過永票,在 HTML 中指定事件處理程序有兩個(gè)缺點(diǎn)卵贱。首先滥沫,存在一個(gè)時(shí)差問題。因?yàn)橛脩艨赡軙?huì)在 HTML 元素一出現(xiàn)在頁(yè)面上就觸發(fā)相應(yīng)的事件键俱,但當(dāng)時(shí)的事件處理程序有可能尚不具備執(zhí)行條件兰绣。
??以前面的例子來(lái)說明,假設(shè) showMessage() 函數(shù)是在按鈕下方编振、頁(yè)面的最底部定義的缀辩。如果用戶在頁(yè)面解析 showMessage() 函數(shù)之前就單擊了按鈕,就會(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ò)展事件處理程序的作用域鏈在不同瀏覽器中會(huì)導(dǎo)致不同結(jié)果畦浓。不同 JavaScript 引擎遵循的標(biāo)識(shí)符解析規(guī)則略有差異,很可能會(huì)在訪問非限定對(duì)象成員時(shí)出錯(cuò)检疫。

??通過 HTML 指定事件處理程序的最后一個(gè)缺點(diǎn)是 HTML 與 JavaScript 代碼緊密耦合讶请。如果要更換事件處理程序,就要改動(dòng)兩個(gè)地方:HTML 代碼和 JavaScript 代碼屎媳。而這正是許多開發(fā)人員摒棄 HTML 事件處理程序夺溢,轉(zhuǎn)而使用 JavaScript 指定事件處理程序的原因所在。

2.2烛谊、 DOM0 級(jí)事件處理程序

??通過 JavaScript 指定事件處理程序的傳統(tǒng)方式振诬,就是將一個(gè)函數(shù)賦值給一個(gè)事件處理程序?qū)傩浴?br> ??這種為事件處理程序賦值的方法是在第四代 Web 瀏覽器中出現(xiàn)的何址,而且至今仍然為所有現(xiàn)代瀏覽器所支持。原因一是簡(jiǎn)單,二是具有跨瀏覽器的優(yōu)勢(shì)祟昭。
??要使用 JavaScript 指定事件處理程序迂求,首先必須取得一個(gè)要操作的對(duì)象的引用坞古。每個(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)前元素哎壳。來(lái)看一個(gè)例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    alert(this.id); // "myBtn"
};

??單擊按鈕顯示的是元素的 ID尚卫,這個(gè) ID 是通過 this.id 取得的归榕。

??不僅僅是 ID,實(shí)際上可以在事件處理程序中通過 this 訪問元素的任何屬性和方法吱涉。以這種方式添加的事件處理程序會(huì)在事件流的冒泡階段被處理刹泄。

??也可以刪除通過 DOM0 級(jí)方法指定的事件處理程序,只要像下面這樣將事件處理程序?qū)傩缘闹翟O(shè)置為 null 即可:

btn.onclick = null; // 刪除事件處理程序

??將事件處理程序設(shè)置為 null 之后怎爵,再單擊按鈕將不會(huì)有任何動(dòng)作發(fā)生特石。

??如果你使用 HTML 指定事件處理程序,那么 onclick 屬性的值就是一個(gè)包含著在同名 HTML 特性中指定的代碼的函數(shù)鳖链。而將相應(yīng)的屬性設(shè)置為 null姆蘸,也可以刪除以這種方式指定的事件處理程序。

2.3芙委、 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 事件添加事件處理程序,可以使用下列代碼:

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è)事件處理程序协饲。來(lái)看下面的例子畏腕。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id);
}, false);

btn.addEventListener("click", function(){
    alert("Hello world!");
}, false);

??這里為按鈕添加了兩個(gè)事件處理程序。這兩個(gè)事件處理程序會(huì)按照添加它們的順序觸發(fā)茉稠,因此首先會(huì)顯示元素的 ID描馅,其次會(huì)顯示"Hello world!"消息。

??通過 addEventListener() 添加的事件處理程序只能使用 removeEventListener() 來(lái)移除而线;移除時(shí)傳入的參數(shù)與添加處理程序時(shí)使用的參數(shù)相同铭污。這也意味著通過 addEventListener() 添加的匿名函數(shù)將無(wú)法移除,如下面的例子所示膀篮。

var btn = document.getElementById("myBtn");

btn.addEventListener("click", function(){
    alert(this.id); 
}, false);

btn.removeEventListener("click", function(){ // 沒有用嘹狞!
    alert(this.id);
}, false);

??在上述例子中,我們使用 addEventListener() 添加了一個(gè)事件處理程序誓竿。雖然調(diào)用 removeEventListener() 時(shí)看似使用了相同的參數(shù)磅网,但實(shí)際上,第二個(gè)參數(shù)與傳入 addEventListener() 中的那一個(gè)是完全不同的函數(shù)烤黍。而傳入 removeEventListener() 中的事件處理程序函數(shù)必須與傳入 addEventListener() 中的相同知市,如下面的例子所示。

var btn = document.getElementById("myBtn");
var handler = function(){
    alert(this.id);
};

btn.addEventListener("click", handler, false);

btn.removeEventListener("click", handler, false); // 有效速蕊!

??重寫后的這個(gè)例子沒有問題嫂丙,是因?yàn)樵?addEventListener() 和 removeEventListener() 中使用了相同的函數(shù)。

??大多數(shù)情況下规哲,都是將事件處理程序添加到事件流的冒泡階段跟啤,這樣可以最大限度地兼容各種瀏覽器。最好只在需要在事件到達(dá)目標(biāo)之前截獲它的時(shí)候?qū)⑹录幚沓绦蛱砑拥讲东@階段唉锌。如果不是特別需要隅肥,我們不建議在事件捕獲階段注冊(cè)事件處理程序。

IE9袄简、Firefox腥放、Safari、Chrome 和 Opera 支持 DOM2 級(jí)事件處理程序绿语。

2.4秃症、 IE 事件處理程序

??IE 實(shí)現(xiàn)了與 DOM 中類似的兩個(gè)方法:attachEvent() 和 detachEvent()。

??這兩個(gè)方法接受相同的兩個(gè)參數(shù):事件處理程序名稱與事件處理程序函數(shù)吕粹。

??由于 IE8 及更早版本只支持事件冒泡种柑,所以通過 attachEvent() 添加的事件處理程序都會(huì)被添加到冒泡階段。

??要使用 attachEvent() 為按鈕添加一個(gè)事件處理程序匹耕,可以使用以下代碼聚请。

var btn = document.getElementById("myBtn");

btn.attachEvent("onclick", function(){
    alert("Clicked");
});

??注意,attachEvent() 的第一個(gè)參數(shù)是"onclick"稳其,而非 DOM 的 addEventListener() 方法中的"click"驶赏。

??在 IE 中使用 attachEvent() 與使用 DOM0 級(jí)方法的主要區(qū)別在于事件處理程序的作用域炸卑。在使用 DOM0 級(jí)方法的情況下链韭,事件處理程序會(huì)在其所屬元素的作用域內(nèi)運(yùn)行摘刑;在使用 attachEvent() 方法的情況下瞧掺,事件處理程序會(huì)在全局作用域中運(yùn)行,因此 this 等于 window夺巩。來(lái)看下面的例子。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert(this === window); // true
});

??在編寫跨瀏覽器的代碼時(shí)周崭,牢記這一區(qū)別非常重要柳譬。

??與 addEventListener() 類似,attachEvent() 方法也可以用來(lái)為一個(gè)元素添加多個(gè)事件處理程序续镇。來(lái)看下面的例子美澳。

var btn = document.getElementById("myBtn");

btn.attachEvent("onclick", function(){
    alert("Clicked");
});

btn.attachEvent("onclick", function(){
    alert("Hello world!");
});

??這里調(diào)用了兩次 attachEvent(),為同一個(gè)按鈕添加了兩個(gè)不同的事件處理程序。

??不過制跟,與 DOM 方法不同的是舅桩,這些事件處理程序不是以添加它們的順序執(zhí)行,而是以相反的順序被觸發(fā)雨膨。單擊這個(gè)例子中的按鈕擂涛,首先看到的是"Hello world!",然后才是"Clicked"聊记。

??使用 attachEvent() 添加的事件可以通過 detachEvent() 來(lái)移除撒妈,條件是必須提供相同的參數(shù)。
??與 DOM 方法一樣排监,這也意味著添加的匿名函數(shù)將不能被移除踩身。不過,只要能夠?qū)?duì)相同函數(shù)的引用傳給 detachEvent()社露,就可以移除相應(yīng)的事件處理程序挟阻。例如:

var btn = document.getElementById("myBtn");
var handler = function(){
    alert("Clicked");
};

btn.attachEvent("onclick", handler);

btn.detachEvent("onclick", handler);

??上述例子將保存在變量 handler 中的函數(shù)作為事件處理程序。因此峭弟,后面的 detachEvent() 可以使用相同的函數(shù)來(lái)移除事件處理程序附鸽。

支持 IE 事件處理程序的瀏覽器有 IE 和 Opera。

2.5瞒瘸、 跨瀏覽器的事件處理程序

??為了以跨瀏覽器的方式處理事件坷备,不少開發(fā)人員會(huì)使用能夠隔離瀏覽器差異的 JavaScript 庫(kù),還有一些開發(fā)人員會(huì)自己開發(fā)最合適的事件處理的方法情臭。自己編寫代碼其實(shí)也不難省撑,只要恰當(dāng)?shù)厥褂媚芰z測(cè)即可。

??要保證處理事件的代碼能在大多數(shù)瀏覽器下一致地運(yùn)行俯在,只需關(guān)注冒泡階段竟秫。

??第一個(gè)要?jiǎng)?chuàng)建的方法是 addHandler(),它的職責(zé)是視情況分別使用 DOM0 級(jí)方法跷乐、DOM2 級(jí)方法或 IE 方法來(lái)添加事件肥败。這個(gè)方法屬于一個(gè)名叫 EventUtil 的對(duì)象,這里將使用這個(gè)對(duì)象來(lái)處理瀏覽器間的差異愕提。

??addHandler() 方法接受 3 個(gè)參數(shù):要操作的元素馒稍、事件名稱和事件處理程序函數(shù)。

??與 addHandler() 對(duì)應(yīng)的方法是 removeHandler()浅侨,它也接受相同的參數(shù)纽谒。這個(gè)方法的職責(zé)是移除之前添加的事件處理程序——無(wú)論該事件處理程序是采取什么方式添加到元素中的,如果其他方法無(wú)效如输,默認(rèn)采用 DOM0 級(jí)方法鼓黔。

??EventUtil 的用法如下所示央勒。

var EventUtil = {

    addHandler: function(element, type, handler){
        if (element.addEventListener){
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent){
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    },

    removeHandler: function(element, type, handler){
        if (element.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í)方法。如果存在 DOM2 級(jí)方法请祖,則使用該方法:傳入事件類型、事件處理程序函數(shù)和第三個(gè)參數(shù) false(表示冒泡階段)脖祈。
??如果存在的是 IE 的方法肆捕,則采取第二種方案。注意盖高,為了在 IE8 及更早版本中運(yùn)行慎陵,此時(shí)的事件類型必須加上"on"前綴。
??最后一種可能就是使用 DOM0 級(jí)方法(在現(xiàn)代瀏覽器中冲甘,應(yīng)該不會(huì)執(zhí)行這里的代碼)县踢。此時(shí)邻悬,我們使用的是方括號(hào)語(yǔ)法來(lái)將屬性名指定為事件處理程序,或者將屬性設(shè)置為 null润梯。

??可以像下面這樣使用 EventUtil 對(duì)象:

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è)事件處理程序舶赔。好在,只支持 DOM0 級(jí)的瀏覽器已經(jīng)沒有那么多了谦秧,因此這對(duì)你而言應(yīng)該不是什么問題竟纳。

3、 事件對(duì)象

??在觸發(fā) DOM 上的某個(gè)事件時(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ì)象蹬挺,但支持方式不同。

3.1它掂、 DOM 中的事件對(duì)象

??兼容 DOM 的瀏覽器會(huì)將一個(gè) event 對(duì)象傳入到事件處理程序中巴帮。無(wú)論指定事件處理程序時(shí)使用什么方法(DOM0 級(jí)或 DOM2 級(jí))溯泣,都會(huì)傳入 event 對(duì)象。來(lái)看下面的例子榕茧。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
    alert(event.type); // "click"
};

btn.addEventListener("click", function(event){
    alert(event.type); // "click"
}, false);

??這個(gè)例子中的兩個(gè)事件處理程序都會(huì)彈出一個(gè)警告框垃沦,顯示由 event.type 屬性表示的事件類型。這個(gè)屬性始終都會(huì)包含被觸發(fā)的事件類型用押,例如"click"(與傳入 addEventListener() 和 removeEventListener() 中的事件類型一致)肢簿。

??在通過 HTML 特性指定事件處理程序時(shí),變量 event 中保存著 event 對(duì)象蜻拨。請(qǐng)看下面的例子池充。

<input type="button" 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()(DOM3級(jí)事件中新增)
detail Integer 只讀 與事件相關(guān)的細(xì)節(jié)信息
eventPhase Integer 只讀 調(diào)用事件處理程序的階段:1表示捕獲階段夹纫,2表示“處于目標(biāo)”序苏,3表示冒泡階段
preventDefault() Function 只讀 取消事件的默認(rèn)行為。如果cancelable是true捷凄,則可以使用這個(gè)方法
stopImmediatePropagation() Function 只讀 取消事件的進(jìn)一步捕獲或冒泡忱详,同時(shí)阻止任何事件處理程序被調(diào)用(DOM3級(jí)事件中新增)
stopPropagation() Function 只讀 取消事件的進(jìn)一步捕獲或冒泡。如果bubbles為true跺涤,則可以使用這個(gè)方法
target Element 只讀 事件的目標(biāo)
trusted Boolean 只讀 為true表示事件是瀏覽器生成的匈睁。為false表示事件是由開發(fā)人員通過 JavaScript 創(chuàng)建的(DOM3級(jí)事件中新增)
type String 只讀 被觸發(fā)的事件的類型
view AbstractView 只讀 與事件關(guān)聯(lián)的抽象視圖。等同于發(fā)生事件的window對(duì)象

??在事件處理程序內(nèi)部桶错,對(duì)象 this 始終等于 currentTarget 的值航唆,而 target 則只包含事件的實(shí)際目標(biāo)。如果直接將事件處理程序指定給了目標(biāo)元素院刁,則 this糯钙、currentTarget 和 target 包含相同的值。來(lái)看下面的例子退腥。

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疾就,因?yàn)槭录幚沓绦蚴亲?cè)到這個(gè)元素上的。然而艺蝴,target 元素卻等于按鈕元素猬腰,因?yàn)樗?click 事件真正的目標(biāo)。

??由于按鈕上并沒有注冊(cè)事件處理程序猜敢,結(jié)果 click 事件就冒泡到了 document.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 "mouseout":
            event.target.style.backgroundColor = "";
            break;
    }
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;

??上述例子定義了一個(gè)名為 handler 的函數(shù)兰英,用于處理 3 種事件:click撇叁、mouseover 和 mouseout。
??當(dāng)單擊按鈕時(shí)畦贸,會(huì)出現(xiàn)一個(gè)與前面例子中一樣的警告框陨闹。當(dāng)按鈕移動(dòng)到按鈕上面時(shí),背景顏色應(yīng)該會(huì)變成紅色薄坏,而當(dāng)鼠標(biāo)移動(dòng)出按鈕的范圍時(shí)趋厉,背景顏色應(yīng)該會(huì)恢復(fù)為默認(rèn)值。
??這里通過檢測(cè) event.type 屬性胶坠,讓函數(shù)能夠確定發(fā)生了什么事件君账,并執(zhí)行相應(yīng)的操作。

??要阻止特定事件的默認(rèn)行為沈善,可以使用 preventDefault() 方法乡数。例如,鏈接的默認(rèn)行為就是在被單擊時(shí)會(huì)導(dǎo)航到其 href 特性指定的 URL闻牡。如果你想阻止鏈接導(dǎo)航這一默認(rèn)行為净赴,那么通過鏈接的 onclick 事件處理程序可以取消它,如下面的例子所示罩润。

var link = document.getElementById("myLink");
link.onclick = function(event){
    event.preventDefault();
};

??只有 cancelable 屬性設(shè)置為 true 的事件玖翅,才可以使用 preventDefault() 來(lái)取消其默認(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");
}; 

??對(duì)于這個(gè)例子而言魔吐,如果不調(diào)用 stopPropagation()扎筒,就會(huì)在單擊按鈕時(shí)出現(xiàn)兩個(gè)警告框〕昴罚可是嗜桌,由于 click 事件根本不會(huì)傳播到 document.body,因此就不會(huì)觸發(fā)注冊(cè)在這個(gè)元素上的 onclick 事件處理程序辞色。

??事件對(duì)象的 eventPhase 屬性骨宠,可以用來(lái)確定事件當(dāng)前正位于事件流的哪個(gè)階段。
??如果是在捕獲階段調(diào)用的事件處理程序相满,那么 eventPhase 等于 1层亿;如果事件處理程序處于目標(biāo)對(duì)象上,則 eventPhase 等于 2立美;如果是在冒泡階段調(diào)用的事件處理程序匿又,eventPhase 等于 3。
??這里要注意的是建蹄,盡管“處于目標(biāo)”發(fā)生在冒泡階段碌更,但 eventPhase 仍然一直等于 2。來(lái)看下面的例子洞慎。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
    alert(event.eventPhase); // 2
};
document.body.addEventListener("click", function(event){
    alert(event.eventPhase); // 1
}, true);
document.body.onclick = function(event){
    alert(event.eventPhase); // 3
};

??當(dāng)單擊這個(gè)例子中的按鈕時(shí)痛单,首先執(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 對(duì)象就會(huì)被銷毀。

3.2院仿、 IE 中的事件對(duì)象

??與訪問 DOM 中的 event 對(duì)象不同秸抚,要訪問 IE 中的 event 對(duì)象有幾種不同的方式,取決于指定事件處理程序的方法歹垫。

??在使用 DOM0 級(jí)方法添加事件處理程序時(shí)剥汤,event 對(duì)象作為 window 對(duì)象的一個(gè)屬性存在。來(lái)看下面的例子排惨。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    var event = window.event;
    alert(event.type); // "click"
};

??在此吭敢,我們通過 window.event 取得了 event 對(duì)象,并檢測(cè)了被觸發(fā)事件的類型(IE 中的 type 屬性與 DOM 中的 type 屬性是相同的)暮芭÷雇眨可是欲低,如果事件處理程序是使用 attachEvent() 添加的,那么就會(huì)有一個(gè) event 對(duì)象作為參數(shù)被傳入事件處理程序函數(shù)中畜晰,如下所示伸头。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event){
    alert(event.type); // "click"
});

??在像這樣使用 attachEvent() 的情況下,也可以通過 window 對(duì)象來(lái)訪問 event 對(duì)象,就像使用 DOM0 級(jí)方法時(shí)一樣汤善。不過為方便起見液茎,同一個(gè)對(duì)象也會(huì)作為參數(shù)傳遞。

??如果是通過 HTML 特性指定的事件處理程序扫步,那么還可以通過一個(gè)名叫 event 的變量來(lái)訪問 event 對(duì)象(與 DOM 中的事件模型相同)。再看一個(gè)例子匈子。

<input type="button" 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ì)象都會(huì)包含下表所列的屬性和方法。

屬性/方法 類 型 讀/寫 說 明
cancelBubble Boolean ???讀/寫 默認(rèn)值為false其徙,但將其設(shè)置為true就可以取消事件冒泡(與DOM中的stopPropagation()方法的作用相同)
returnValue Boolean 讀/寫 默認(rèn)值為true胚迫,但將其設(shè)置為false就可以取消事件的默認(rèn)行為(與DOM中的preventDefault()方法的作用相同)
srcElement Element 只讀 事件的目標(biāo)(與DOM中的target屬性相同)
type String 只讀 被觸發(fā)的事件的類型

??因?yàn)槭录幚沓绦虻淖饔糜蚴歉鶕?jù)指定它的方式來(lái)確定的,所以不能認(rèn)為 this 會(huì)始終等于事件目標(biāo)唾那。故而访锻,最好還是使用 event.srcElement 比較保險(xiǎn)。例如:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    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)行為。來(lái)看下面的例子轨功。

var link = document.getElementById("myLink");
link.onclick = function(){
    window.event.returnValue = false;
}; 

??這個(gè)例子在onclick 事件處理程序中使用 returnValue 達(dá)到了阻止鏈接默認(rèn)行為的目的旭斥。與 DOM 不同的是,在此沒有辦法確定事件是否能被取消古涧。

??相應(yīng)地垂券,cancelBubble 屬性與 DOM 中的 stopPropagation() 方法作用相同,都是用來(lái)停止事件冒泡的。由于 IE 不支持事件捕獲菇爪,因而只能取消事件冒泡算芯;但 stopPropagatioin() 可以同時(shí)取消事件捕獲和冒泡。例如:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    alert("Clicked");
    window.event.cancelBubble = true;
};
document.body.onclick = function(){
    alert("Body clicked");
};

??通過在 onclick 事件處理程序中將 cancelBubble 設(shè)置為 true凳宙,就可阻止事件通過冒泡而觸發(fā) document.body 中注冊(cè)的事件處理程序熙揍。結(jié)果,在單擊按鈕之后氏涩,只會(huì)顯示一個(gè)警告框届囚。

3.3、 跨瀏覽器的事件對(duì)象

??雖然 DOM 和 IE 中的 event 對(duì)象不同是尖,但基于它們之間的相似性依舊可以拿出跨瀏覽器的方案來(lái)意系。
??IE 中 event 對(duì)象的全部信息和方法 DOM 對(duì)象中都有,只不過實(shí)現(xiàn)方式不一樣饺汹。不過蛔添,這種對(duì)應(yīng)關(guān)系讓實(shí)現(xiàn)兩種事件模型之間的映射非常容易《荡牵可以對(duì)前面介紹的 EventUtil 對(duì)象加以增強(qiáng)迎瞧,添加如下方法以求同存異。

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){
        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;
        }
    }
};

??以上代碼顯示逸吵,我們?yōu)?EventUtil 添加了 4 個(gè)新方法凶硅。

??第一個(gè)是 getEvent(),它返回對(duì) event 對(duì)象的引用胁塞∮匠ⅲ考慮到 IE 中事件對(duì)象的位置不同压语,可以使用這個(gè)方法來(lái)取得 event 對(duì)象啸罢,而不必?fù)?dān)心指定事件處理程序的方式。在使用這個(gè)方法時(shí)胎食,必須假設(shè)有一個(gè)事件對(duì)象傳入到事件處理程序中扰才,而且要把該變量傳給這個(gè)方法,如下所示厕怜。

btn.onclick = function(event){
    event = EventUtil.getEvent(event);
};

??在兼容 DOM 的瀏覽器中衩匣,event 變量只是簡(jiǎn)單地傳入和返回。而在 IE 中粥航,event 參數(shù)是未定義的(undefined)琅捏,因此就會(huì)返回 window.event。將這一行代碼添加到事件處理程序的開頭递雀,就可以確保隨時(shí)都能使用 event 對(duì)象柄延,而不必?fù)?dān)心用戶使用的是什么瀏覽器。

??第二個(gè)方法是 getTarget()缀程,它返回事件的目標(biāo)搜吧。在這個(gè)方法內(nèi)部市俊,會(huì)檢測(cè) event 對(duì)象的 target 屬性,如果存在則返回該屬性的值滤奈;否則摆昧,返回 srcElement 屬性的值⊙殉蹋可以像下面這樣使用這個(gè)方法绅你。

btn.onclick = function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
};

??第三個(gè)方法是 preventDefault(),用于取消事件的默認(rèn)行為昭躺。在傳入 event 對(duì)象后勇吊,這個(gè)方法會(huì)檢查是否存在 preventDefault() 方法,如果存在則調(diào)用該方法窍仰。如果 preventDefault() 方法不存在汉规,則將 returnValue 設(shè)置為 false。下面是使用這個(gè)方法的例子驹吮。

var link = document.getElementById("myLink");
link.onclick = function(event){
    event = EventUtil.getEvent(event);
    EventUtil.preventDefault(event);
};

??以上代碼可以確保在所有瀏覽器中單擊該鏈接都不會(huì)打開另一個(gè)頁(yè)面针史。首先,使用 EventUtil.getEvent() 取得 event 對(duì)象碟狞,然后將其傳入到 EventUtil.preventDefault() 以取消默認(rèn)行為啄枕。

??第四個(gè)方法是 stopPropagation(),其實(shí)現(xiàn)方式類似族沃。首先嘗試使用 DOM 方法阻止事件流频祝,否則就使用 cancelBubble 屬性。下面看一個(gè)例子脆淹。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
    alert("Clicked");
    event = EventUtil.getEvent(event);
    EventUtil.stopPropagation(event);
};
document.body.onclick = function(event){
    alert("Body clicked");
};

??在此常空,首先使用 EventUtil.getEvent() 取得了 event 對(duì)象,然后又將其傳入到 EventUtil.stopPropagation()盖溺。別忘了由于 IE 不支持事件捕獲漓糙,因此這個(gè)方法在跨瀏覽器的情況下,也只能用來(lái)阻止事件冒泡烘嘱。

4昆禽、 事件類型

??Web 瀏覽器中可能發(fā)生的事件有很多類型。如前所述蝇庭,不同的事件類型具有不同的信息醉鳖,而“DOM3 級(jí)事件”規(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ù)讓?DOM 結(jié)構(gòu)發(fā)生變化時(shí)觸發(fā)社搅。
  • 變動(dòng)名稱事件,當(dāng)元素或?qū)傩悦儎?dòng)時(shí)觸發(fā)乳规。此類事件已經(jīng)被廢棄形葬,沒有任何瀏覽器實(shí)現(xiàn)它們,因此本章不做介紹暮的。

??除了這幾類事件之外笙以,HTML5 也定義了一組事件,而有些瀏覽器還會(huì)在 DOM 和 BOM 中實(shí)現(xiàn)其他專有事件冻辩。這些專有的事件一般都是根據(jù)開發(fā)人員需求定制的猖腕,沒有什么規(guī)范,因此不同瀏覽器的實(shí)現(xiàn)有可能不一致恨闪。

??DOM3 級(jí)事件模塊在 DOM2 級(jí)事件模塊基礎(chǔ)上重新定義了這些事件倘感,也添加了一些新事件。包括 IE9 在內(nèi)的所有主流瀏覽器都支持 DOM2 級(jí)事件凛剥。IE9 也支持 DOM3 級(jí)事件侠仇。

4.1、 UI 事件

??UI 事件指的是那些不一定與用戶操作有關(guān)的事件犁珠。這些事件在 DOM 規(guī)范出現(xiàn)之前,都是以這種或那種形式存在的互亮,而在 DOM 規(guī)范中保留是為了向后兼容±缦恚現(xiàn)有的 UI 事件如下。

  • DOMActivate:表示元素已經(jīng)被用戶操作(通過鼠標(biāo)或鍵盤)激活豹休。這個(gè)事件在 DOM3 級(jí)事件中被廢棄炊昆,但 Firefox 2+和 Chrome 支持它。考慮到不同瀏覽器實(shí)現(xiàn)的差異凤巨,不建議使用這個(gè)事件视乐。
  • 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)無(wú)法加載圖像時(shí)在 <img> 元素上面觸發(fā)碉哑,當(dāng)無(wú)法加載嵌入內(nèi)容時(shí)在<object>元素上面觸發(fā)登颓,或者當(dāng)有一或多個(gè)框架無(wú)法加載時(shí)在框架集上面觸發(fā)顶瞒。
  • select:當(dāng)用戶選擇文本框(<input>或<texterea>)中的一或多個(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)煮盼。

??除了 DOMActivate 之外凄吏,其他事件在 DOM2 級(jí)事件中都?xì)w為 HTML 事件(DOMActivate 在 DOM2 級(jí)中仍然屬于 UI 事件)远舅。要確定瀏覽器是否支持 DOM2 級(jí)事件規(guī)定的 HTML 事件,可以使用如下代碼:

var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");

??注意痕钢,只有根據(jù)“DOM2 級(jí)事件”實(shí)現(xiàn)這些事件的瀏覽器才會(huì)返回 true图柏。而以非標(biāo)準(zhǔn)方式支持這些事件的瀏覽器則會(huì)返回 false。要確定瀏覽器是否支持“DOM3 級(jí)事件”定義的事件任连,可以使用如下代碼:

var isSupported = document.implementation.hasFeature("UIEvent", "3.0");
1. load 事件

??JavaScript 中最常用的一個(gè)事件就是 load蚤吹。當(dāng)頁(yè)面完全加載后(包括所有圖像、JavaScript 文件随抠、CSS 文件等外部資源)裁着,就會(huì)觸發(fā) window 上面的 load 事件。

??有兩種定義 onload 事件處理程序的方式拱她。第一種方式是使用如下所示的 JavaScript 代碼:

EventUtil.addHandler(window, "load", function(event){
    alert("Loaded!");
});

??這是通過 JavaScript 來(lái)指定事件處理程序的方式二驰,使用了本章前面定義的跨瀏覽器的 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>
<head> 
    <title>Load Event Example</title>
</head>
<body onload="alert('Loaded!')">

</body>
</html>

??一般來(lái)說棘捣,在 window 上面發(fā)生的任何事件都可以在<body>元素中通過相應(yīng)的特性來(lái)指定辜腺,因?yàn)樵?HTML 中無(wú)法訪問 window 元素。實(shí)際上柱锹,這只是為了保證向后兼容的一種權(quán)宜之計(jì)哪自,但所有瀏覽器都能很好地支持這種方式。

??我們建議讀者盡可能使用 JavaScript 方式禁熏。

??根據(jù)“DOM2 級(jí)事件”規(guī)范壤巷,應(yīng)該在 document 而非 window 上面觸發(fā) load 事件。但是瞧毙,所有瀏覽器都在 window 上面實(shí)現(xiàn)了該事件胧华,以確保向后兼容。

??圖像上面也可以觸發(fā) load 事件宙彪,無(wú)論是在 DOM 中的圖像元素還是 HTML 中的圖像元素矩动。因此,可以在 HTML 中為任何圖像指定 onload 事件處理程序释漆,例如:

<img src="smile.gif" onload="alert('Image loaded.')"> 

??這樣悲没,當(dāng)例子中的圖像加載完畢后就會(huì)顯示一個(gè)警告框。同樣的功能也可以使用 JavaScript 來(lái)實(shí)現(xiàn)男图,例如:

var image = document.getElementById("myImage");
EventUtil.addHandler(image, "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";
});

??在這個(gè)例子中,首先為 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ì)開始下載屋厘。

??同樣的功能也可以通過使用 DOM0 級(jí)的 Image 對(duì)象實(shí)現(xiàn)。在 DOM 出現(xiàn)之前月而,開發(fā)人員經(jīng)常使用 Image 對(duì)象在客戶端預(yù)先加載圖像汗洒。可以像使用<img>元素一樣使用 Image 對(duì)象父款,只不過無(wú)法將其添加到 DOM 樹中溢谤。下面來(lái)看一個(gè)例子。

EventUtil.addHandler(window, "load", function(){
    var image = new Image();
    EventUtil.addHandler(image, "load", function(event){
        alert("Image loaded!");
    });
    image.src = "smile.gif";
}); 

??在此憨攒,我們使用 Image 構(gòu)造函數(shù)創(chuàng)建了一個(gè)新圖像的實(shí)例世杀,然后又為它指定了事件處理程序。有的瀏覽器將 Image 對(duì)象實(shí)現(xiàn)為<img>元素肝集,但并非所有瀏覽器都如此瞻坝,所以最好將它們區(qū)別對(duì)待。

??在不屬于 DOM 文檔的圖像(包括未添加到文檔的<img>元素和 Image 對(duì)象)上觸發(fā) load 事件時(shí)杏瞻,IE8 及之前版本不會(huì)生成 event 對(duì)象所刀。IE9 修復(fù)了這個(gè)問題。

??還有一些元素也以非標(biāo)準(zhǔn)的方式支持 load 事件伐憾。在 IE9+勉痴、Firefox、Opera树肃、Chrome 和 Safari 3+及更高版本中蒸矛,<script>元素也會(huì)觸發(fā) load 事件,以便開發(fā)人員確定動(dòng)態(tài)加載的 JavaScript 文件是否加載完畢胸嘴。
??與圖像不同雏掠,只有在設(shè)置了<script>元素的 src 屬性并將該元素添加到文檔后,才會(huì)開始下載 JavaScript 文件劣像。換句話說乡话,對(duì)于<script>元素而言,指定 src 屬性和指定事件處理程序的先后順序就不重要了耳奕。以下代碼展示了怎樣為<script>元素指定事件處理程序绑青。

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);
});

??這個(gè)例子使用了跨瀏覽器的 EventUtil 對(duì)象為新創(chuàng)建的<script>元素指定了onload 事件處理程序诬像。此時(shí),大多數(shù)瀏覽器中 event 對(duì)象的 target 屬性引用的都是<script>節(jié)點(diǎn)闸婴,而在 Firefox 3 之前的版本中坏挠,引用的則是 document。IE8 及更早版本不支持<script>元素上的 load 事件邪乍。

??IE 和 Opera 還支持<link>元素上的 load 事件降狠,以便開發(fā)人員確定樣式表是否加載完畢。例如:

EventUtil.addHandler(window, "load", function(){
    var link = document.createElement("link");
    link.type = "text/css";
    link.rel= "stylesheet";
    EventUtil.addHandler(link, "load", function(event){
        alert("css loaded");
    });
    link.href = "example.css";
    document.getElementsByTagName("head")[0].appendChild(link);
});

??與<script>節(jié)點(diǎn)類似庇楞,在未指定 href 屬性并將<link>元素添加到文檔之前也不會(huì)開始下載樣式表榜配。

2. unload 事件

??與 load 事件對(duì)應(yīng)的是 unload 事件,這個(gè)事件在文檔被完全卸載后觸發(fā)吕晌。只要用戶從一個(gè)頁(yè)面切換到另一個(gè)頁(yè)面蛋褥,就會(huì)發(fā)生 unload 事件。而利用這個(gè)事件最多的情況是清除引用聂使,以避免內(nèi)存泄漏壁拉。

??與 load 事件類似,也有兩種指定 onunload 事件處理程序的方式弃理。第一種方式是使用 JavaScript,如下所示:

EventUtil.addHandler(window, "unload", function(event){
    alert("Unloaded");
});

??此時(shí)生成的 event 對(duì)象在兼容 DOM 的瀏覽器中只包含 target 屬性(值為 document)辆苔。IE8 及之前版本則為這個(gè)事件對(duì)象提供了 srcElement 屬性扼劈。

??指定事件處理程序的第二種方式驻啤,也是為<body>元素添加一個(gè)特性(與 load 事件相似),如下面的例子所示:

<!DOCTYPE html>
<html>
<head>
    <title>Unload Event Example</title>
</head>
<body onunload="alert('Unloaded!')">

</body>
</html>

??無(wú)論使用哪種方式荐吵,都要小心編寫 onunload 事件處理程序中的代碼骑冗。既然 unload 事件是在一切都被卸載之后才觸發(fā),那么在頁(yè)面加載后存在的那些對(duì)象先煎,此時(shí)就不一定存在了贼涩。此時(shí)占锯,操作 DOM 節(jié)點(diǎn)或者元素的樣式就會(huì)導(dǎo)致錯(cuò)誤歪脏。

??根據(jù)“DOM2 級(jí)事件”,應(yīng)該在<body>元素而非 window 對(duì)象上面觸發(fā) unload事件。不過,所有瀏覽器都在 window 上實(shí)現(xiàn)了 unload 事件,以確保向后兼容。

3. resize 事件

??當(dāng)瀏覽器窗口被調(diào)整到一個(gè)新的高度或?qū)挾葧r(shí),就會(huì)觸發(fā) resize 事件。這個(gè)事件在 window(窗口)上面觸發(fā)磨镶,因此可以通過 JavaScript 或者<body>元素中的 onresize 特性來(lái)指定事件處理程序脐嫂。如前所述,我們還是推薦使用如下所示的 JavaScript 方式:

EventUtil.addHandler(window, "resize", function(event){
    alert("Resized");
});

??與其他發(fā)生在 window 上的事件類似,在兼容 DOM 的瀏覽器中,傳入事件處理程序中的 event 對(duì)象有一個(gè) target 屬性,值為 document咪啡;而 IE8 及之前版本則未提供任何屬性愁溜。

??關(guān)于何時(shí)會(huì)觸發(fā) resize 事件渐扮,不同瀏覽器有不同的機(jī)制耻讽。IE、Safari、Chrome 和 Opera 會(huì)在瀏覽器窗口變化了 1 像素時(shí)就觸發(fā) resize 事件,然后隨著變化不斷重復(fù)觸發(fā)锹雏。Firefox 則只會(huì)在用戶停止調(diào)整窗口大小時(shí)才會(huì)觸發(fā) resize 事件佣耐。由于存在這個(gè)差別,應(yīng)該注意不要在這個(gè)事件的處理程序中加入大計(jì)算量的代碼,因?yàn)檫@些代碼有可能被頻繁執(zhí)行,從而導(dǎo)致瀏覽器反應(yīng)明顯變慢。

??瀏覽器窗口最小化或最大化時(shí)也會(huì)觸發(fā) resize 事件。

4. scroll 事件

??雖然 scroll 事件是在 window 對(duì)象上發(fā)生的,但它實(shí)際表示的則是頁(yè)面中相應(yīng)元素的變化。
??在混雜模式下年碘,可以通過<body>元素的 scrollLeft 和 scrollTop 來(lái)監(jiān)控到這一變化涡尘;
??而在標(biāo)準(zhǔn)模式下川梅,除 Safari 之外的所有瀏覽器都會(huì)通過<html>元素來(lái)反映這一變化(Safari 仍然基于<body>跟蹤滾動(dòng)位置)潮饱,如下面的例子所示:

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)模式不同使用了不同的元素凫碌。由于 Safari 3.1 之前的版本不支持 document.compatMode苦掘,因此舊版本的瀏覽器就會(huì)滿足第二個(gè)條件递瑰。

??與 resize 事件類似乡恕,scroll 事件也會(huì)在文檔被滾動(dòng)期間重復(fù)被觸發(fā)粱胜,所以有必要盡量保持事件處理程序的代碼簡(jiǎn)單叛本。

4.2营搅、 焦點(diǎn)事件

??焦點(diǎn)事件會(huì)在頁(yè)面元素獲得或失去焦點(diǎn)時(shí)觸發(fā)休蟹。利用這些事件并與 document.hasFocus() 方法及 document.activeElement 屬性配合翔怎,可以知曉用戶在頁(yè)面上的行蹤于毙。有以下 6 個(gè)焦點(diǎn)事件介蛉。

  • focus:在元素獲得焦點(diǎn)時(shí)觸發(fā)吹菱。這個(gè)事件不會(huì)冒泡输瓜;所有瀏覽器都支持它芹缔。
  • blur:在元素失去焦點(diǎn)時(shí)觸發(fā)蚜点。這個(gè)事件不會(huì)冒泡陪拘;所有瀏覽器都支持它欠痴。
  • focusin:在元素獲得焦點(diǎn)時(shí)觸發(fā)菩咨。這個(gè)事件與 HTML 事件 focus 等價(jià)摩钙,但它冒泡长踊。支持這個(gè)事件的瀏覽器有 IE5.5+、Safari 5.1+、Opera 11.5+和 Chrome。
  • focusout:在元素失去焦點(diǎn)時(shí)觸發(fā)郎任。這個(gè)事件是 HTML 事件 blur 的通用版本杂抽。支持這個(gè)事件的瀏覽器有 IE5.5+杭朱、Safari 5.1+刃唐、Opera 11.5+和 Chrome抖甘。
  • DOMFocusIn:在元素獲得焦點(diǎn)時(shí)觸發(fā)昼接。這個(gè)事件與 HTML 事件 focus 等價(jià)漂辐,但它冒泡纬纪。只有 Opera 支持這個(gè)事件。DOM3 級(jí)事件廢棄了 DOMFocusIn,選擇了 focusin。
  • DOMFocusOut:在元素失去焦點(diǎn)時(shí)觸發(fā)钠惩。這個(gè)事件是 HTML 事件 blur 的通用版本蔬咬。只有 Opera 支持這個(gè)事件狐援。DOM3 級(jí)事件廢棄了 DOMFocusOut镶殷,選擇了 focusout。

??這一類事件中最主要的兩個(gè)是 focus 和 blur,它們都是 JavaScript 早期就得到所有瀏覽器支持的事件梗逮。這些事件的最大問題是它們不冒泡岁诉。因此坠韩,IE 的 focusin 和 focusout 與 Opera 的 DOMFocusIn 和 DOMFocusOut 才會(huì)發(fā)生重疊氢惋。IE 的方式最后被 DOM3 級(jí)事件采納為標(biāo)準(zhǔn)方式循未。

??當(dāng)焦點(diǎn)從頁(yè)面中的一個(gè)元素移動(dòng)到另一個(gè)元素嫂粟,會(huì)依次觸發(fā)下列事件:
????(1) focusout 在失去焦點(diǎn)的元素上觸發(fā);
????(2) focusin 在獲得焦點(diǎn)的元素上觸發(fā);
????(3) blur 在失去焦點(diǎn)的元素上觸發(fā)吃溅;
????(4) DOMFocusOut 在失去焦點(diǎn)的元素上觸發(fā)甩苛;
????(5) focus 在獲得焦點(diǎn)的元素上觸發(fā);
????(6) DOMFocusIn 在獲得焦點(diǎn)的元素上觸發(fā)。

??其中,blur梨睁、DOMFocusOut 和 focusout 的事件目標(biāo)是失去焦點(diǎn)的元素拳亿;而 focus、DOMFocusIn 和 focusin 的事件目標(biāo)是獲得焦點(diǎn)的元素。
??要確定瀏覽器是否支持這些事件,可以使用如下代碼:

var isSupported = document.implementation.hasFeature("FocusEvent", "3.0");

??即使 focus 和 blur 不冒泡,也可以在捕獲階段偵聽到它們涡真。

4.3杏节、 鼠標(biāo)與滾輪事件

??鼠標(biāo)事件是 Web 開發(fā)中最常用的一類事件,畢竟鼠標(biāo)還是最主要的定位設(shè)備。DOM3 級(jí)事件中定義了 9 個(gè)鼠標(biāo)事件,簡(jiǎn)介如下:

  • click:在用戶單擊主鼠標(biāo)按鈕(一般是左邊的按鈕)或者按下回車鍵時(shí)觸發(fā)司志。這一點(diǎn)對(duì)確保易訪問性很重要拓型,意味著 onclick 事件處理程序既可以通過鍵盤也可以通過鼠標(biāo)執(zhí)行捕儒。
  • dblclick:在用戶雙擊主鼠標(biāo)按鈕(一般是左邊的按鈕)時(shí)觸發(fā)扇调。從技術(shù)上說莲镣,這個(gè)事件并不是 DOM2 級(jí)事件規(guī)范中規(guī)定的半火,但鑒于它得到了廣泛支持,左移 DOM3 級(jí)事件將其納入了標(biāo)準(zhǔn)。
  • mousedown:在用戶按下了任意鼠標(biāo)按鈕時(shí)觸發(fā)。不能通過鍵盤觸發(fā)這個(gè)事件。
  • mouseup:在用戶設(shè)防鼠標(biāo)按鈕時(shí)觸發(fā)阅羹。不能通過鍵盤觸發(fā)這個(gè)事件轨淌。
  • mouseenter:在鼠標(biāo)光標(biāo)從元素外部首次移動(dòng)到元素范圍之內(nèi)時(shí)觸發(fā)。這個(gè)事件不冒泡,而且在光標(biāo)移動(dòng)到后代元素上不會(huì)觸發(fā)语婴。DOM2 級(jí)事件并沒有定義這個(gè)事件缠导,但 DOM3 級(jí)事件將它納入了規(guī)范竹挡。IE、Firefox 9+ 和 Opera 支持這個(gè)事件。
  • mouseleave:在位于元素上方的鼠標(biāo)光標(biāo)移動(dòng)到元素范圍之外時(shí)觸發(fā)隙券。這個(gè)事件不冒泡,而且在光標(biāo)移動(dòng)到后代元素上不會(huì)觸發(fā)铐刘。DOM2 級(jí)事件并沒有定義這個(gè)事件盼产,但 DOM3 級(jí)事件將它納入了規(guī)范芹关。IE、Firefox 9+ 和 Opera 支持這個(gè)事件。
  • mousemove:當(dāng)鼠標(biāo)指針在元素內(nèi)部移動(dòng)時(shí)觸發(fā)虑灰。不能通過鍵盤觸發(fā)這個(gè)事件对湃。
  • mouseout:在鼠標(biāo)指針位于一個(gè)元素上方,然后用戶將其移入另一個(gè)元素時(shí)觸發(fā)。又移入的另一個(gè)元素可能位于前一個(gè)元素的外部潘飘,也可能是這個(gè)元素的子元素筐高。不能通過鍵盤觸發(fā)這個(gè)事件扮宠。
  • mouseover轿偎;在鼠標(biāo)指針位于一個(gè)元素外部蜓斧,然后用戶將其首次移入另一個(gè)元素邊界之內(nèi)時(shí)觸發(fā)直奋。不能通過鍵盤觸發(fā)這個(gè)事件渠旁。

??頁(yè)面上的所有元素都支持鼠標(biāo)事件。除了 mouseenter 和 mouseleave,所有鼠標(biāo)事件都會(huì)冒泡防泵,也可以被取消,而取消鼠標(biāo)事件將會(huì)影響瀏覽器的默認(rèn)行為码泞。取消鼠標(biāo)事件的默認(rèn)行為還會(huì)影響其它事件,因?yàn)槭髽?biāo)事件與其它事件是密不可分的關(guān)系。

??只有在同一個(gè)元素上相繼觸發(fā) mousedown 和 mouseup 事件,才會(huì)觸發(fā) click 事件续膳;如果 mousedown 或 mouseup 中的一個(gè)被取消改艇,就不會(huì)觸發(fā) click 事件。
??類似地,只有觸發(fā)兩次 click 事件盏缤,才會(huì)觸發(fā)一次 dblclick 事件熬甫。如果有代碼阻止了連續(xù)兩次觸發(fā) click 事件(可能是直接取消 click 事件辈双,也可能通過取消 mousedown 或 mouseup 間接實(shí)現(xiàn))湃密,那么就不會(huì)觸發(fā) dblclick 事件了赃磨。這 4 個(gè)事件觸發(fā)的順序始終如下:
????(1) mousedown
????(2) mouseup
????(3) click
????(4) mousedown
????(5) mouseup
????(6) click
????(7) dblclick

??顯然,click 和 dblclick 事件都會(huì)依賴于其他先行事件的觸發(fā);而 mousedown 和 mouseup 則不受其他事件的影響。

??IE8 及之前版本中的實(shí)現(xiàn)有一個(gè)小 bug城舞,因此在雙擊事件中绰精,會(huì)跳過第二個(gè) mousedown 和 click 事件妻往,其順序如下:
????(1) mousedown
????(2) mouseup
????(3) click
????(4) mouseup
????(5) dblclick

??IE9 修復(fù)了這個(gè) bug,之后順序就正確了。

??使用以下代碼可以檢測(cè)瀏覽器是否支持以上 DOM2 級(jí)事件(除 dbclick掌实、mouseenter 和 mouseleave 之外):

var isSupported = document.implementation.hasFeature("MouseEvents", "2.0");

??要檢測(cè)瀏覽器是否支持上面的所有事件芋齿,可以使用以下代碼:

var isSupported = document.implementation.hasFeature("MouseEvent", "3.0")

??注意扛禽,DOM3 級(jí)事件的 feature 名是"MouseEvent"拟赊,而非"MouseEvents"。

??鼠標(biāo)事件中還有一類滾輪事件墩弯。而說是一類事件羊娃,其實(shí)就是一個(gè) mousewheel 事件。這個(gè)事件跟蹤鼠標(biāo)滾輪亦歉,類似于 Mac 的觸控板。

1. 客戶區(qū)坐標(biāo)位置

??鼠標(biāo)事件都是在瀏覽器視口中的特定位置上發(fā)生的籽慢。這個(gè)位置信息保存在事件對(duì)象的 clientX 和 clientY 屬性中。所有瀏覽器都支持這兩個(gè)屬性痊远,它們的值表示事件發(fā)生時(shí)鼠標(biāo)指針在視口中的水平和垂直坐標(biāo)年堆。下圖展示了視口中客戶區(qū)坐標(biāo)位置的含義。

??可以使用類似下列代碼取得鼠標(biāo)事件的客戶端坐標(biāo)信息:

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Client coordinates: " + event.clientX + "," + event.clientY);
});

??這里為一個(gè)<div>元素指定了 onclick 事件處理程序熙侍。當(dāng)用戶單擊這個(gè)元素時(shí)售担,就會(huì)看到事件的客戶端坐標(biāo)信息衫仑。

??注意切黔,這些值中不包括頁(yè)面滾動(dòng)的距離荧恍,因此這個(gè)位置并不表示鼠標(biāo)在頁(yè)面上的位置投储。

2. 頁(yè)面坐標(biāo)位置

??通過客戶區(qū)坐標(biāo)能夠知道鼠標(biāo)是在視口中什么位置發(fā)生的躯泰,而頁(yè)面坐標(biāo)通過事件對(duì)象的 pageX 和 pageY 屬性,能告訴你事件是在頁(yè)面中的什么位置發(fā)生的貌笨。換句話說,這兩個(gè)屬性表示鼠標(biāo)光標(biāo)在頁(yè)面中的位置轿曙,因此坐標(biāo)是從頁(yè)面本身而非視口的左邊和頂邊計(jì)算的哮独。

??以下代碼可以取得鼠標(biāo)事件在頁(yè)面中的坐標(biāo):

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Page coordinates: " + event.pageX + "," + event.pageY);
}); 

??在頁(yè)面沒有滾動(dòng)的情況下拳芙,pageX 和 pageY 的值與 clientX 和 clientY 的值相等。

??IE8 及更早版本不支持事件對(duì)象上的頁(yè)面坐標(biāo)皮璧,不過使用客戶區(qū)坐標(biāo)和滾動(dòng)信息可以計(jì)算出來(lái)舟扎。這時(shí)候需要用到 document.body(混雜模式)或 document.documentElement(標(biāo)準(zhǔn)模式)中的 scrollLeft 和 scrollTop 屬性。計(jì)算過程如下所示:

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);

    var pageX = event.pageX,
        pageY = event.pageY;

    if (pageX === undefined){
        pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
    }
    if (pageY === undefined){
        pageY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop);
    }
    alert("Page coordinates: " + pageX + "," + pageY);
});
3. 屏幕坐標(biāo)位置

??鼠標(biāo)事件發(fā)生時(shí)悴务,不僅會(huì)有相對(duì)于瀏覽器窗口的位置睹限,還有一個(gè)相對(duì)于整個(gè)電腦屏幕的位置。而通過 screenX 和 screenY 屬性就可以確定鼠標(biāo)事件發(fā)生時(shí)鼠標(biāo)指針相對(duì)于整個(gè)屏幕的坐標(biāo)信息讯檐。

??可以使用類似下面的代碼取得鼠標(biāo)事件的屏幕坐標(biāo):

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Screen coordinates: " + event.screenX + "," + event.screenY);
});

??與前一個(gè)例子類似羡疗,這里也是為<div>元素指定了一個(gè) onclick 事件處理程序。當(dāng)這個(gè)元素被單擊時(shí)别洪,就會(huì)顯示出事件的屏幕坐標(biāo)信息了叨恨。

4. 修改鍵

??雖然鼠標(biāo)事件主要是使用鼠標(biāo)來(lái)觸發(fā)的,但在按下鼠標(biāo)時(shí)鍵盤上的某些鍵的狀態(tài)也可以影響到所要采取的操作挖垛。這些修改鍵就是 Shift痒钝、Ctrl、Alt 和 Meta(在 Windows 鍵盤中是 Windows 鍵痢毒,在蘋果機(jī)中是 Cmd 鍵)送矩,它們經(jīng)常被用來(lái)修改鼠標(biāo)事件的行為。

??DOM 為此規(guī)定了 4 個(gè)屬性哪替,表示這些修改鍵的狀態(tài):shiftKey栋荸、ctrlKey、altKey 和 metaKey。這些屬性中包含的都是布爾值晌块,如果相應(yīng)的鍵被按下了爱沟,則值為 true,否則值為 false摸袁。
??當(dāng)某個(gè)鼠標(biāo)事件發(fā)生時(shí)荣月,通過檢測(cè)這幾個(gè)屬性就可以確定用戶是否同時(shí)按下了其中的鍵初坠。來(lái)看下面的例子樟蠕。

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);

    var keys = new Array();

    if (event.shiftKey){
        keys.push("shift");
    }
    if (event.ctrlKey){
        keys.push("ctrl");
    }
    if (event.altKey){
        keys.push("alt");
    }
    if (event.metaKey){
        keys.push("meta");
    }
    alert("Keys: " + keys.join(","));
});

??在這個(gè)例子中颓鲜,我們通過一個(gè) onclick 事件處理程序檢測(cè)了不同修改鍵的狀態(tài)苍息。數(shù)組 keys 中包含著被按下的修改鍵的名稱庄敛。換句話說伟叛,如果有屬性值為 true略就,就會(huì)將對(duì)應(yīng)修改鍵的名稱添加到 keys 數(shù)組中兄墅。在事件處理程序的最后踢星,有一個(gè)警告框?qū)z測(cè)到的鍵的信息顯示給用戶。

??IE9隙咸、Firefox沐悦、Safari、Chrome 和 Opera 都支持這 4 個(gè)鍵五督。IE8 及之前版本不支持 metaKey 屬性藏否。

5. 相關(guān)元素

??在發(fā)生 mouseover 和 mouseout 事件時(shí),還會(huì)涉及更多的元素充包。
??這兩個(gè)事件都會(huì)涉及把鼠標(biāo)指針從一個(gè)元素的邊界之內(nèi)移動(dòng)到另一個(gè)元素的邊界之內(nèi)副签。
??對(duì) mouseover 事件而言,事件的主目標(biāo)是獲得光標(biāo)的元素基矮,而相關(guān)元素就是那個(gè)失去光標(biāo)的元素淆储。
??對(duì) mouseout 事件而言,事件的主目標(biāo)是失去光標(biāo)的元素家浇,而相關(guān)元素則是獲得光標(biāo)的元素本砰。來(lái)看下面的例子。

<!DOCTYPE html>
<html>
<head>
    <title>Related Elements Example</title>
</head>
<body>
    <div id="myDiv" style="background-color:red;height:100px;width:100px;"></div>
</body>
</html>

??上述例子會(huì)在頁(yè)面上顯示一個(gè)<div>元素钢悲。如果鼠標(biāo)指針一開始位于這個(gè)<div>元素上点额,然后移出了這個(gè)元素,那么就會(huì)在<div>元素上觸發(fā) mouseout 事件譬巫,相關(guān)元素就是<body>元素咖楣。與此同時(shí),<body>元素上面會(huì)觸發(fā) mouseover 事件芦昔,而相關(guān)元素變成了<div>诱贿。

??DOM 通過 event 對(duì)象的 relatedTarget 屬性提供了相關(guān)元素的信息。這個(gè)屬性只對(duì)于 mouseover 和mouseout 事件才包含值;對(duì)于其他事件珠十,這個(gè)屬性的值是null料扰。
??IE8及之前版本不支持 relatedTarget 屬性,但提供了保存著同樣信息的不同屬性焙蹭。在 mouseover 事件觸發(fā)時(shí)晒杈,IE 的 fromElement 屬性中保存了相關(guān)元素;在 mouseout 事件觸發(fā)時(shí)孔厉,IE 的 toElement 屬性中保存著相關(guān)元素拯钻。(IE9 支持所有這些屬性。)
??可以把下面這個(gè)跨瀏覽器取得相關(guān)元素的方法添加到 EventUtil 對(duì)象中撰豺。

var EventUtil = {
    // 省略了其他代碼
    getRelatedTarget: function(event){
        if (event.relatedTarget){
            return event.relatedTarget;
        } else if (event.toElement){
            return event.toElement;
        } else if (event.fromElement){
            return event.fromElement;
        } else {
            return null;
        }
    },
    // 省略了其他代碼
}; 

??與以前添加的跨瀏覽器方法一樣粪般,這個(gè)方法也使用了特性檢測(cè)來(lái)確定返回哪個(gè)值∥坭耄可以像下面這樣使用 EventUtil.getRelatedTarget() 方法:

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mouseout", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    var relatedTarget = EventUtil.getRelatedTarget(event);
    alert("Moused out of " + target.tagName + " to " + relatedTarget.tagName);
});

??這個(gè)例子為<div>元素的 mouseout 事件注冊(cè)了一個(gè)事件處理程序亩歹。當(dāng)事件觸發(fā)時(shí),會(huì)有一個(gè)警告框顯示鼠標(biāo)移出和移入的元素信息凡橱。

6. 鼠標(biāo)按鈕

??只有在主鼠標(biāo)按鈕被單擊(或鍵盤回車鍵被按下)時(shí)才會(huì)觸發(fā) click 事件小作,因此檢測(cè)按鈕的信息并不是必要的。但對(duì)于 mousedown 和 mouseup 事件來(lái)說稼钩,則在其 event 對(duì)象存在一個(gè) button 屬性顾稀,表示按下或釋放的按鈕。

??DOM 的 button 屬性可能有如下 3 個(gè)值:0 表示主鼠標(biāo)按鈕变抽,1 表示中間的鼠標(biāo)按鈕(鼠標(biāo)滾輪按鈕)础拨,2 表示次鼠標(biāo)按鈕。在常規(guī)的設(shè)置中绍载,主鼠標(biāo)按鈕就是鼠標(biāo)左鍵诡宗,而次鼠標(biāo)按鈕就是鼠標(biāo)右鍵。

??IE8 及之前版本也提供了 button 屬性击儡,但這個(gè)屬性的值與 DOM 的 button 屬性有很大差異塔沃。

  • 0:表示沒有按下按鈕。
  • 1:表示按下了主鼠標(biāo)按鈕阳谍。
  • 2:表示按下了次鼠標(biāo)按鈕蛀柴。
  • 3:表示同時(shí)按下了主、次鼠標(biāo)按鈕矫夯。
  • 4:表示按下了中間的鼠標(biāo)按鈕鸽疾。
  • 5:表示同時(shí)按下了主鼠標(biāo)按鈕和中間的鼠標(biāo)按鈕。
  • 6:表示同時(shí)按下了次鼠標(biāo)按鈕和中間的鼠標(biāo)按鈕训貌。
  • 7:表示同時(shí)按下了三個(gè)鼠標(biāo)按鈕制肮。

??不難想見冒窍,DOM 模型下的 button 屬性比 IE 模型下的 button 屬性更簡(jiǎn)單也更為實(shí)用,因?yàn)橥瑫r(shí)按下多個(gè)鼠標(biāo)按鈕的情形十分罕見豺鼻。
??最常見的做法就是將 IE 模型規(guī)范化為 DOM 方式综液,畢竟除 IE8 及更早版本之外的其他瀏覽器都原生支持 DOM 模型。而對(duì)主儒飒、中谬莹、次按鈕的映射并不困難,只要將 IE 的其他選項(xiàng)分別轉(zhuǎn)換成如同按下這三個(gè)按鍵中的一個(gè)即可(同時(shí)將主按鈕作為優(yōu)先選取的對(duì)象)桩了。換句話說附帽,IE 中返回的 5 和 7 會(huì)被轉(zhuǎn)換成 DOM 模型中的 0。

??由于單獨(dú)使用能力檢測(cè)無(wú)法確定差異(兩種模型有同名的 button 屬性)圣猎,因此必須另辟蹊徑士葫。我們知道乞而,支持 DOM 版鼠標(biāo)事件的瀏覽器可以通過 hasFearture() 方法來(lái)檢測(cè)送悔,所以可以再為 EventUtil 對(duì)象添加如下 getButton() 方法。

var EventUtil = {
    // 省略了其他代碼
    getButton: function(event){
        if (document.implementation.hasFeature("MouseEvents", "2.0")){
            return event.button; 
       } else {
            switch(event.button){
                case 0:
                case 1:
                case 3:
                case 5:
                case 7:
                    return 0;
                case 2:
                case 6:
                    return 2;
                case 4:
                    return 1;
            }
        }
    },
    // 省略了其他代碼
};

??通過檢測(cè)"MouseEvents"這個(gè)特性爪模,就可以確定 event 對(duì)象中存在的 button 屬性中是否包含正確的值欠啤。如果測(cè)試失敗,說明是 IE屋灌,就必須對(duì)相應(yīng)的值進(jìn)行規(guī)范化洁段。以下是使用該方法的示例。

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mousedown", function(event){
    event = EventUtil.getEvent(event);
    alert(EventUtil.getButton(event));
});

??在上述例子中共郭,我們?yōu)橐粋€(gè)<div>元素添加了一個(gè) onmousedown 事件處理程序祠丝。當(dāng)在這個(gè)元素上按下鼠標(biāo)按鈕時(shí),會(huì)有警告框顯示按鈕的代碼除嘹。

??在使用 onmouseup 事件處理程序時(shí)写半,button 的值表示釋放的是哪個(gè)按鈕。此外尉咕,如果不是按下或釋放了主鼠標(biāo)按鈕叠蝇,Opera 不會(huì)觸發(fā) mouseup 或 mousedown 事件。

7. 更多的事件信息

??“DOM2 級(jí)事件”規(guī)范在 event 對(duì)象中還提供了 detail 屬性年缎,用于給出有關(guān)事件的更多信息悔捶。
??對(duì)于鼠標(biāo)事件來(lái)說,detail 中包含了一個(gè)數(shù)值单芜,表示在給定位置上發(fā)生了多少次單擊蜕该。在同一個(gè)元素上相繼地發(fā)生一次 mousedown 和一次 mouseup 事件算作一次單擊。
??detail 屬性從 1 開始計(jì)數(shù)洲鸠,每次單擊發(fā)生后都會(huì)遞增堂淡。如果鼠標(biāo)在 mousedown 和 mouseup 之間移動(dòng)了位置,則 detail 會(huì)被重置為 0。

??IE 也通過下列屬性為鼠標(biāo)事件提供了更多信息淤齐。

  • altLeft:布爾值股囊,表示是否按下了 Alt鍵。如果 altLeft 的值為 true更啄,則 altKey 的值也為 true稚疹。
  • ctrlLeft:布爾值,表示是否按下了 Ctrl 鍵祭务。如果 ctrlLeft 的值為 true内狗,則 ctrlKey 的值也為 true。
  • offsetX:光標(biāo)相對(duì)于目標(biāo)元素邊界的 x 坐標(biāo)义锥。
  • offsetY:光標(biāo)相對(duì)于目標(biāo)元素邊界的 y 坐標(biāo)柳沙。
  • shiftLeft:布爾值,表示是否按下了 Shift 鍵拌倍。如果 shiftLeft 的值為 true赂鲤,則 shiftKey 的值也為 true。

??這些屬性的用處并不大柱恤,原因一方面是只有 IE 支持它們数初,另一方是它們提供的信息要么沒有什么價(jià)值,要么可以通過其他方式計(jì)算得來(lái)梗顺。

8. 鼠標(biāo)滾輪事件

??IE 6.0 首先實(shí)現(xiàn)了 mousewheel 事件泡孩。此后,Opera寺谤、Chrome 和 Safari 也都實(shí)現(xiàn)了這個(gè)事件仑鸥。

??當(dāng)用戶通過鼠標(biāo)滾輪與頁(yè)面交互、在垂直方向上滾動(dòng)頁(yè)面時(shí)(無(wú)論向上還是向下)变屁,就會(huì)觸發(fā) mousewheel 事件眼俊。這個(gè)事件可以在任何元素上面觸發(fā),最終會(huì)冒泡到 document(IE8)或 window(IE9敞贡、Opera泵琳、Chrome 及 Safari)對(duì)象。

??與 mousewheel 事件對(duì)應(yīng)的 event 對(duì)象除包含鼠標(biāo)事件的所有標(biāo)準(zhǔn)信息外誊役,還包含一個(gè)特殊的 wheelDelta 屬性获列。當(dāng)用戶向前滾動(dòng)鼠標(biāo)滾輪時(shí),wheelDelta 是 120 的倍數(shù)蛔垢;當(dāng)用戶向后滾動(dòng)鼠標(biāo)滾輪時(shí)击孩,wheelDelta 是-120 的倍數(shù)。下圖展示了這個(gè)屬性鹏漆。

??將 mousewheel 事件處理程序指定給頁(yè)面中的任何元素或 document 對(duì)象巩梢,即可處理鼠標(biāo)滾輪的交互操作创泄。來(lái)看下面的例子。

EventUtil.addHandler(document, "mousewheel", function(event){
    event = EventUtil.getEvent(event);
    alert(event.wheelDelta);
});

??上述例子會(huì)在發(fā)生 mousewheel 事件時(shí)顯示 wheelDelta 的值括蝠。多數(shù)情況下鞠抑,只要知道鼠標(biāo)滾輪滾動(dòng)的方向就夠了,而這通過檢測(cè) wheelDelta 的正負(fù)號(hào)就可以確定忌警。

??有一點(diǎn)要注意:在 Opera 9.5 之前的版本中搁拙,wheelDelta 值的正負(fù)號(hào)是顛倒的。如果你打算支持早期的 Opera 版本法绵,就需要使用瀏覽器檢測(cè)技術(shù)來(lái)確定實(shí)際的值箕速,如下面的例子所示。

EventUtil.addHandler(document, "mousewheel", function(event){
    event = EventUtil.getEvent(event);
    var delta = (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta);
    alert(delta);
}); 

??上述代碼使用 client 對(duì)象檢測(cè)了瀏覽器是不是早期版本的 Opera朋譬。

??由于 mousewheel 事件非常流行盐茎,而且所有瀏覽器都支持它,所以 HTML 5 也加入了該事件徙赢。

??Firefox 支持一個(gè)名為 DOMMouseScroll 的類似事件字柠,也是在鼠標(biāo)滾輪滾動(dòng)時(shí)觸發(fā)。與 mousewheel 事件一樣犀忱,DOMMouseScroll 也被視為鼠標(biāo)事件募谎,因而包含與鼠標(biāo)事件有關(guān)的所有屬性。
??而有關(guān)鼠標(biāo)滾輪的信息則保存在 detail 屬性中阴汇,當(dāng)向前滾動(dòng)鼠標(biāo)滾輪時(shí),這個(gè)屬性的值是-3 的倍數(shù)节槐,當(dāng)向后滾動(dòng)鼠標(biāo)滾輪時(shí)搀庶,這個(gè)屬性的值是 3 的倍數(shù)。下圖展示了這個(gè)屬性铜异。

??可以將 DOMMouseScroll 事件添加到頁(yè)面中的任何元素哥倔,而且該事件會(huì)冒泡到 window 對(duì)象肛捍。因此番甩,可以像下面這樣針對(duì)這個(gè)事件來(lái)添加事件處理程序。

EventUtil.addHandler(window, "DOMMouseScroll", function(event){
    event = EventUtil.getEvent(event);
    alert(event.detail);
});

??這個(gè)簡(jiǎn)單的事件處理程序會(huì)在鼠標(biāo)滾輪滾動(dòng)時(shí)顯示 detail 屬性的值秩冈。

??若要給出跨瀏覽器環(huán)境下的解決方案蚂子,第一步就是創(chuàng)建一個(gè)能夠取得鼠標(biāo)滾輪增量值(delta)的方法沃测。下面是我們添加到 EventUtil 對(duì)象中的這個(gè)方法。

var EventUtil = {

    // 省略了其他代碼
    getWheelDelta: function(event){
        if (event.wheelDelta){
            return (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta);
        } else {
            return -event.detail * 40;
        } 
    },
    // 省略了其他代碼
}; 

??這里食茎,getWheelDelta() 方法首先檢測(cè)了事件對(duì)象是否包含 wheelDelta 屬性蒂破,如果是則通過瀏覽器檢測(cè)代碼確定正確的值。如果 wheelDelta 不存在别渔,則假設(shè)相應(yīng)的值保存在 detail 屬性中附迷。由于 Firefox 的值有所不同惧互,因此首先要將這個(gè)值的符號(hào)反向,然后再乘以 40喇伯,就可以保證與其他瀏覽器的值相同了喊儡。
??有了這個(gè)方法之后,就可以將相同的事件處理程序指定給 mousewheel 和 DOMMouseScroll 事件了稻据,例如:

(function(){
    function handleMouseWheel(event){
        event = EventUtil.getEvent(event);
        var delta = EventUtil.getWheelDelta(event);
        alert(delta);
    }

    EventUtil.addHandler(document, "mousewheel", handleMouseWheel);
    EventUtil.addHandler(document, "DOMMouseScroll", handleMouseWheel);
})();

??我們將相關(guān)代碼放在了一個(gè)私有作用域中管宵,從而不會(huì)讓新定義的函數(shù)干擾全局作用域。這里定義的 handleMouseWheel() 函數(shù)可以用作兩個(gè)事件的處理程序(如果指定的事件不存在攀甚,則為該事件指定處理程序的代碼就會(huì)靜默地失斅崞印)。由于使用了 EventUtil.getWheelDelta() 方法秋度,我們定義的這個(gè)事件處理程序函數(shù)可以適用于任何一種情況炸庞。

9. 觸摸設(shè)備

??iOS 和 Android 設(shè)備的實(shí)現(xiàn)非常特別,因?yàn)檫@些設(shè)備沒有鼠標(biāo)荚斯。在面向 iPhone 和 iPod 中的 Safari 開發(fā)時(shí)埠居,要記住以下幾點(diǎn)。

  • 不支持 dblclick 事件事期。雙擊瀏覽器窗口會(huì)放大畫面滥壕,而且沒有辦法改變?cè)撔袨椤?/li>
  • 輕擊可單擊元素會(huì)觸發(fā) mousemove 事件。如果此操作會(huì)導(dǎo)致內(nèi)容變化兽泣,將不再有其他事件發(fā)生绎橘;如果屏幕沒有因此變化,那么會(huì)依次發(fā)生 mousedown唠倦、mouseup 和 click 事件称鳞。輕擊不可單擊的元素不會(huì)觸發(fā)任何事件〕肀牵可單擊的元素是指那些單擊可產(chǎn)生默認(rèn)操作的元素(如鏈接)冈止,或者那些已經(jīng)被指定了onclick 事件處理程序的元素。
  • mousemove 事件也會(huì)觸發(fā) mouseover 和 mouseout 事件候齿。
  • 兩個(gè)手指放在屏幕上且頁(yè)面隨手指移動(dòng)而滾動(dòng)時(shí)會(huì)觸發(fā) mousewheel 和 scroll 事件熙暴。
10. 無(wú)障礙性問題

??如果你的 Web 應(yīng)用程序或網(wǎng)站要確保殘疾人特別是那些使用屏幕閱讀器的人都能訪問,那么在使用鼠標(biāo)事件時(shí)就要格外小心慌盯。
??前面提到過周霉,可以通過鍵盤上的回車鍵來(lái)觸發(fā) click 事件,但其他鼠標(biāo)事件卻無(wú)法通過鍵盤來(lái)觸發(fā)润匙。
??為此诗眨,我們不建議使用 click 之外的其他鼠標(biāo)事件來(lái)展示功能或引發(fā)代碼執(zhí)行。因?yàn)檫@樣會(huì)給盲人或視障用戶造成極大不便孕讳。以下是在使用鼠標(biāo)事件時(shí)應(yīng)當(dāng)注意的幾個(gè)易訪問性問題匠楚。

  • 使用 click 事件執(zhí)行代碼巍膘。有人指出通過 onmousedown 執(zhí)行代碼會(huì)讓人覺得速度更快,對(duì)視力正常的人來(lái)說這是沒錯(cuò)的芋簿。但是峡懈,在屏幕閱讀器中,由于無(wú)法觸發(fā) mousedown 事件与斤,結(jié)果就會(huì)造成代碼無(wú)法執(zhí)行肪康。
  • 不要使用 onmouseover 向用戶顯示新的選項(xiàng)。原因同上撩穿,屏幕閱讀器無(wú)法觸發(fā)這個(gè)事件磷支。如果確實(shí)非要通過這種方式來(lái)顯示新選項(xiàng),可以考慮添加顯示相同信息的鍵盤快捷方式食寡。
  • 不要使用 dblclick 執(zhí)行重要的操作雾狈。鍵盤無(wú)法觸發(fā)這個(gè)事件。

??遵照以上提示可以極大地提升殘疾人在訪問你的 Web 應(yīng)用程序或網(wǎng)站時(shí)的易訪問性抵皱。

4.4 鍵盤與文本事件

??用戶在使用鍵盤時(shí)會(huì)觸發(fā)鍵盤事件善榛。“DOM2 級(jí)事件”最初規(guī)定了鍵盤事件呻畸,但在最終定稿之前又刪除了相應(yīng)的內(nèi)容移盆。結(jié)果,對(duì)鍵盤事件的支持主要遵循的是 DOM0 級(jí)伤为。

??“DOM3 級(jí)事件”為鍵盤事件制定了規(guī)范咒循,IE9 率先完全實(shí)現(xiàn)了該規(guī)范。其他瀏覽器也在著手實(shí)現(xiàn)這一標(biāo)準(zhǔn)钮呀,但仍然有很多遺留的問題剑鞍。

??有 3 個(gè)鍵盤事件,簡(jiǎn)述如下爽醋。

  • keydown:當(dāng)用戶按下鍵盤上的任意鍵時(shí)觸發(fā),而且如果按住不放的話便脊,會(huì)重復(fù)觸發(fā)此事件蚂四。
  • keypress:當(dāng)用戶按下鍵盤上的字符鍵時(shí)觸發(fā),而且如果按住不放的話哪痰,會(huì)重復(fù)觸發(fā)此事件遂赠。按下 Esc 鍵也會(huì)觸發(fā)這個(gè)事件。Safari 3.1 之前的版本也會(huì)在用戶按下非字符鍵時(shí)觸發(fā) keypress 事件晌杰。
  • keyup:當(dāng)用戶釋放鍵盤上的鍵時(shí)觸發(fā)跷睦。

??雖然所有元素都支持以上 3 個(gè)事件,但只有在用戶通過文本框輸入文本時(shí)才最常用到肋演。

??只有一個(gè)文本事件:textInput抑诸。這個(gè)事件是對(duì) keypress 的補(bǔ)充烂琴,用意是在將文本顯示給用戶之前更容易攔截文本。在文本插入文本框之前會(huì)觸發(fā) textInput 事件蜕乡。

??在用戶按了一下鍵盤上的字符鍵時(shí)奸绷,首先會(huì)觸發(fā) keydown 事件,然后緊跟著是 keypress 事件层玲,最后會(huì)觸發(fā) keyup 事件号醉。其中,keydown 和 keypress 都是在文本框發(fā)生變化之前被觸發(fā)的辛块;而 keyup 事件則是在文本框已經(jīng)發(fā)生變化之后被觸發(fā)的畔派。
??如果用戶按下了一個(gè)字符鍵不放,就會(huì)重復(fù)觸發(fā) keydown 和 keypress 事件润绵,直到用戶松開該鍵為止线椰。

??如果用戶按下的是一個(gè)非字符鍵,那么首先會(huì)觸發(fā) keydown 事件授药,然后就是 keyup 事件士嚎。如果按住這個(gè)非字符鍵不放,那么就會(huì)一直重復(fù)觸發(fā) keydown 事件悔叽,直到用戶松開這個(gè)鍵莱衩,此時(shí)會(huì)觸發(fā) keyup 事件。

??鍵盤事件與鼠標(biāo)事件一樣娇澎,都支持相同的修改鍵笨蚁。而且,鍵盤事件的事件對(duì)象中也有 shiftKey趟庄、ctrlKey括细、altKey 和 metaKey 屬性。IE 不支持 metaKey戚啥。

1. 鍵碼

??在發(fā)生 keydown 和 keyup 事件時(shí)奋单,event 對(duì)象的 keyCode 屬性中會(huì)包含一個(gè)代碼,與鍵盤上一個(gè)特定的鍵對(duì)應(yīng)猫十。
??對(duì)數(shù)字字母字符鍵览濒,keyCode 屬性的值與 ASCII 碼中對(duì)應(yīng)小寫字母或數(shù)字的編碼相同。因此拖云,數(shù)字鍵 7 的 keyCode 值為 55贷笛,而字母 A 鍵的 keyCode 值為 65——與 Shift 鍵的狀態(tài)無(wú)關(guān)。
??DOM 和 IE 的 event 對(duì)象都支持 keyCode 屬性宙项。請(qǐng)看下面這個(gè)例子:

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keyup", function(event){
    event = EventUtil.getEvent(event);
    alert(event.keyCode);
});

??在上述例子中乏苦,用戶每次在文本框中按鍵觸發(fā) keyup 事件時(shí),都會(huì)顯示 keyCode 的值尤筐。下表列出了所有非字符鍵的鍵碼汇荐。

鍵 碼 鍵 碼
退格(Backspace) 8 數(shù)字小鍵盤1 97
制表(Tab) 9 數(shù)字小鍵盤2 98
回車(Enter) 13 數(shù)字小鍵盤3 99
上檔(Shift) 16 數(shù)字小鍵盤4 100
控制(Ctrl) 17 數(shù)字小鍵盤5 101
Alt 18 數(shù)字小鍵盤6 102
暫停/中斷(Pause/Break) 19 數(shù)字小鍵盤7 103
大寫鎖定(Caps Lock) 20 數(shù)字小鍵盤8 104
退出(Esc) 27 數(shù)字小鍵盤9 105
上翻頁(yè)(Page Up) 33 數(shù)字小鍵盤0 96
下翻頁(yè)(Page Down) 34 數(shù)字小鍵盤+ 107
結(jié)尾(End) 35 數(shù)字小鍵盤及大鍵盤上的- 109
開頭(Home) 36 數(shù)字小鍵盤 . 110
左箭頭(Left Arrow) 37 數(shù)字小鍵盤 / 111
上箭頭(Up Arrow) 38 F1 112
右箭頭(Right Arrow) 39 F2 113
下箭頭(Down Arrow) 40 F3 114
插入(Ins) 45 F4 115
刪除(Del) 46 F5 116
左Windows鍵 91 F6 117
右Windows鍵 92 F7 118
上下文菜單鍵 93 F8 119
數(shù)字鎖(Num Lock) 144 F9 120
滾動(dòng)鎖(Scroll Lock) 145 F10 121
左方括號(hào) 219 F11 122
右方括號(hào) 221 F12 123
大于 190 正斜杠 191
小于 188 反斜杠(\) 220
等于 61 沉音符(`) 192
分號(hào)(IE/Safari/Chrome中) 186 單引號(hào) 222
分號(hào)(Opera/FF中) 59

??無(wú)論 keydown 或 keyup 事件都會(huì)存在的一些特殊情況洞就。在 Firefox和 Opera中,按分號(hào)鍵時(shí) keyCode 值為 59拢驾,也就是 ASCII 中分號(hào)的編碼奖磁;但 IE 和 Safari 返回 186,即鍵盤中按鍵的鍵碼繁疤。

2. 字符編碼

??發(fā)生 keypress 事件意味著按下的鍵會(huì)影響到屏幕中文本的顯示咖为。在所有瀏覽器中,按下能夠插入或刪除字符的鍵都會(huì)觸發(fā) keypress 事件稠腊;按下其他鍵能否觸發(fā)此事件因?yàn)g覽器而異躁染。
??由于截止到 2008年,尚無(wú)瀏覽器實(shí)現(xiàn)“DOM3 級(jí)事件”規(guī)范架忌,所以瀏覽器之間的鍵盤事件并沒有多大的差異吞彤。
??IE9、Firefox叹放、Chrome 和 Safari 的 event 對(duì)象都支持一個(gè) charCode 屬性饰恕,這個(gè)屬性只有在發(fā)生 keypress 事件時(shí)才包含值,而且這個(gè)值是按下的那個(gè)鍵所代表字符的 ASCII 編碼井仰。此時(shí)的 keyCode 通常等于 0 或者也可能等于所按鍵的鍵碼埋嵌。
??IE8及之前版本和 Opera 則是在 keyCode 中保存字符的 ASCII 編碼。要想以跨瀏覽器的方式取得字符編碼俱恶,必須首先檢測(cè) charCode 屬性是否可用雹嗦,如果不可用則使用 keyCode,如下面的例子所示合是。

var EventUtil = {

    // 省略的代碼
    getCharCode: function(event){
        if (typeof event.charCode == "number"){
            return event.charCode;
        } else {
            return event.keyCode;
        }
    },
    // 省略的代碼
};

??這個(gè)方法首先檢測(cè) charCode 屬性是否包含數(shù)值(在不支持這個(gè)屬性的瀏覽器中了罪,值為 undefined),如果是聪全,則返回該值泊藕。否則,就返回 keyCode 屬性值难礼。下面是使用這個(gè)方法的示例吱七。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event); 
    alert(EventUtil.getCharCode(event));
});

??在取得了字符編碼之后,就可以使用 String.fromCharCode() 將其轉(zhuǎn)換成實(shí)際的字符鹤竭。

3. DOM3 級(jí)變化

??盡管所有瀏覽器都實(shí)現(xiàn)了某種形式的鍵盤事件,DOM3 級(jí)事件還是做出了一些改變景醇。比如臀稚,DOM3 級(jí)事件中的鍵盤事件,不再包含 charCode 屬性三痰,而是包含兩個(gè)新屬性:key 和 char吧寺。

??其中窜管,key 屬性是為了取代 keyCode 而新增的,它的值是一個(gè)字符串稚机。在按下某個(gè)字符鍵時(shí)幕帆,key 的值就是相應(yīng)的文本字符(如“k”或“M”);在按下非字符鍵時(shí)赖条, key 的值是相應(yīng)鍵的名(如“Shift” 或 “Down”)失乾。
??而 char 屬性在按下字符鍵時(shí)的行為與 key 相同,但在按下非字符鍵時(shí)值為 null纬乍。

??IE9 支持 key 屬性碱茁,但不支持 char 屬性。Safari 5 和 Chrome 支持名為 keyIdentifier 的屬性仿贬,在按下非字符鍵(例如 Shift)的情況下與 key 的值相同纽竣。
??對(duì)于字符鍵,keyIdentifier 返回一個(gè)格式類似“U+0000”的字符串茧泪,表示 Unicode 值蜓氨。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var identifier = event.key || event.keyIdentifier;
    if (identifier){
        alert(identifier);
    }
});

??由于存在跨瀏覽器問題,因此本書不推薦使用 key队伟、keyIdentifier 或 char禀综。

??DOM3 級(jí)事件還添加了一個(gè)名為 location 的屬性,這是一個(gè)數(shù)值铛楣,表示按下了什么位置上的鍵:0 表示默認(rèn)鍵盤弱左,1 表示左側(cè)位置(例如左位的 Alt 鍵),2 表示右側(cè)位置(例如右側(cè)的 Shift 鍵)棘钞,3 表示數(shù)字小鍵盤缠借,4 表示移動(dòng)設(shè)備鍵盤(也就是虛擬鍵盤),5 表示手柄(如任天堂 Wii 控制器)宜猜。
??IE9 支持這個(gè)屬性泼返。Safari 和 Chrome 支持名為 keyLocation 的等價(jià)屬性,但即有 bug——值始終是 0姨拥,除非按下了數(shù)字鍵盤(此時(shí)绅喉,值 為 3);否則叫乌,不會(huì)是 1柴罐、2、4憨奸、5革屠。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var loc = event.location || event.keyLocation;
    if (loc){
        alert(loc);
    }
});

??與 key 屬性一樣,支持 location 的瀏覽器也不多,所以在跨瀏覽器開發(fā)中不推薦使用似芝。

??最后是給 event 對(duì)象添加了 getModifierState() 方法那婉。這個(gè)方法接收一個(gè)參數(shù),即等于 Shift党瓮、Control详炬、AltGraph 或 Meta 的字符串,表示要檢測(cè)的修改鍵寞奸。如果指定的修改鍵是活動(dòng)的(也就是處于被按下的狀態(tài))呛谜,這個(gè)方法返回 true,否則返回 false蝇闭。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    if (event.getModifierState){
        alert(event.getModifierState("Shift"));
    }
});

??實(shí)際上呻率,通過 event 對(duì)象的 shiftKey、altKey呻引、ctrlKey 和 metaKey 屬性已經(jīng)可以取得類似的屬性了礼仗。IE9 是唯一支持 getModifierState() 方法的瀏覽器。

4. textInput 事件

??“DOM3 級(jí)事件”規(guī)范中引入了一個(gè)新事件逻悠,名叫 textInput元践。根據(jù)規(guī)范,當(dāng)用戶在可編輯區(qū)域中輸入字符時(shí)童谒,就會(huì)觸發(fā)這個(gè)事件单旁。
??這個(gè)用于替代 keypress 的 textInput 事件的行為稍有不同。
??區(qū)別之一就是任何可以獲得焦點(diǎn)的元素都可以觸發(fā) keypress 事件饥伊,但只有可編輯區(qū)域才能觸發(fā) textInput事件象浑。
??區(qū)別之二是 textInput 事件只會(huì)在用戶按下能夠輸入實(shí)際字符的鍵時(shí)才會(huì)被觸發(fā),而 keypress 事件則在按下那些能夠影響文本顯示的鍵時(shí)也會(huì)觸發(fā)(例如退格鍵)琅豆。

??由于 textInput 事件主要考慮的是字符愉豺,因此它的 event 對(duì)象中還包含一個(gè) data 屬性,這個(gè)屬性的值就是用戶輸入的字符(而非字符編碼)茫因。換句話說蚪拦,用戶在沒有按上檔鍵的情況下按下了 S 鍵,data 的值就是"s"冻押,而如果在按住上檔鍵時(shí)按下該鍵驰贷,data 的值就是"S"。以下是一個(gè)使用 textInput 事件的例子:

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "textInput", function(event){
    event = EventUtil.getEvent(event);
    alert(event.data);
});

??在上述例子中洛巢,插入到文本框中的字符會(huì)通過一個(gè)警告框顯示出來(lái)括袒。

??另外,event 對(duì)象上還有一個(gè)屬性稿茉,叫 inputMethod箱熬,表示把文本輸入到文本框中的方式类垦。

  • 0,表示瀏覽器不確定是怎么輸入的城须。
  • 1,表示是使用鍵盤輸入的米苹。
  • 2糕伐,表示文本是粘貼進(jìn)來(lái)的。
  • 3蘸嘶,表示文本是拖放進(jìn)來(lái)的良瞧。
  • 4,表示文本是使用 IME 輸入的训唱。
  • 5褥蚯,表示文本是通過在表單中選擇某一項(xiàng)輸入的。
  • 6况增,表示文本是通過手寫輸入的(比如使用手寫筆)赞庶。
  • 7,表示文本是通過語(yǔ)音輸入的澳骤。
  • 8歧强,表示文本是通過幾種方法組合輸入的。
  • 9为肮,表示文本是通過腳本輸入的摊册。

??使用這個(gè)屬性可以確定文本是如何輸入到控件中的,從而可以驗(yàn)證其有效性颊艳。支持 textInput 屬性的瀏覽器有 IE9+茅特、Safari 和 Chrome。只有 IE 支持 inputMethod 屬性棋枕。

5. 設(shè)備中的鍵盤事件

??任天堂 Wii 會(huì)在用戶按下 Wii 遙控器上的按鍵時(shí)觸發(fā)鍵盤事件白修。盡管沒有辦法訪問 Wii 遙控器中的所有按鍵,但還是有一些鍵可以觸發(fā)鍵盤事件戒悠。

??iOS 版 Safari 和 Android 版 WebKit 在使用屏幕鍵盤時(shí)會(huì)觸發(fā)鍵盤事件熬荆。

4.5、 復(fù)合事件

??復(fù)合事件(composition event)是 DOM3 級(jí)事件中新添加的一類事件绸狐,用于處理 IME 的輸入序列卤恳。

??IME(Input Method Editor,輸入法編輯器)可以讓用戶輸入在物理鍵盤上找不到的字符寒矿。例如突琳,使用拉丁文鍵盤的用戶通過 IME 照樣能輸入日文字符。IME 通常需要同時(shí)按住多個(gè)鍵符相,但最終只輸入一個(gè)字符拆融。復(fù)合事件就是針對(duì)檢測(cè)和處理這種輸入而設(shè)計(jì)的蠢琳。有以下三種復(fù)合事件。

  • compositionstart:在 IME 的文本復(fù)合系統(tǒng)打開時(shí)觸發(fā)镜豹,表示要開始輸入了傲须。
  • compositionupdate:在向輸入字段中插入新字符時(shí)觸發(fā)。
  • compositionend:在 IME 的文本復(fù)合系統(tǒng)關(guān)閉時(shí)觸發(fā)趟脂,表示返回正常鍵盤輸入狀態(tài)泰讽。

??復(fù)合事件與文本事件在很多方面都很相似。在觸發(fā)復(fù)合事件時(shí)昔期,目標(biāo)是接收文本的輸入字段已卸。但它比文本事件的事件對(duì)象多一個(gè)屬性 data,其中包含以下幾個(gè)值中的一個(gè):

  • 如果在 compositionstart 事件發(fā)生時(shí)訪問硼一,包含正在編輯的文本(例如累澡,已經(jīng)選中的需要馬上替換的文本);
  • 如果在 compositionupdate 事件發(fā)生時(shí)訪問般贼,包含正插入的新字符愧哟;
  • 如果在 compositionend 事件發(fā)生時(shí)訪問,包含此次輸入會(huì)話中插入的所有字符具伍。

??與文本事件一樣翅雏,必要時(shí)可以利用復(fù)合事件來(lái)篩選輸入∪搜浚可以像下面這樣使用它們:

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "compositionstart", function(event){
    event = EventUtil.getEvent(event);
    alert(event.data);
});
EventUtil.addHandler(textbox, "compositionupdate", function(event){
    event = EventUtil.getEvent(event);
    alert(event.data);
});
EventUtil.addHandler(textbox, "compositionend", function(event){
    event = EventUtil.getEvent(event);
    alert(event.data);
});

??IE9+是到 2011 年唯一支持復(fù)合事件的瀏覽器望几。由于缺少支持,對(duì)于需要開發(fā)跨瀏覽器應(yīng)用的開發(fā)人員萤厅,它的用處不大橄抹。要確定瀏覽器是否支持復(fù)合事件,可以使用以下代碼:

var isSupported = document.implementation.hasFeature("CompositionEvent", "3.0");

4.6惕味、 變動(dòng)事件

??DOM2 級(jí)的變動(dòng)(mutation)事件能在 DOM 中的某一部分發(fā)生變化時(shí)給出提示楼誓。變動(dòng)事件是為 XML 或 HTML DOM 設(shè)計(jì)的,并不特定于某種語(yǔ)言名挥。DOM2 級(jí)定義了如下變動(dòng)事件疟羹。

  • DOMSubtreeModified:在 DOM 結(jié)構(gòu)中發(fā)生任何變化時(shí)觸發(fā)。這個(gè)事件在其他任何事件觸發(fā)后都會(huì)觸發(fā)禀倔。
  • DOMNodeInserted:在一個(gè)節(jié)點(diǎn)作為子節(jié)點(diǎn)被插入到另一個(gè)節(jié)點(diǎn)中時(shí)觸發(fā)榄融。
  • DOMNodeRemoved:在節(jié)點(diǎn)從其父節(jié)點(diǎn)中被移除時(shí)觸發(fā)。
  • DOMNodeInsertedIntoDocument:在一個(gè)節(jié)點(diǎn)被直接插入文檔或通過子樹間接插入文檔之后觸發(fā)救湖。這個(gè)事件在 DOMNodeInserted 之后觸發(fā)愧杯。
  • DOMNodeRemovedFromDocument:在一個(gè)節(jié)點(diǎn)被直接從文檔中移除或通過子樹間接從文檔中移除之前觸發(fā)。這個(gè)事件在 DOMNodeRemoved 之后觸發(fā)鞋既。
  • DOMAttrModified:在特性被修改之后觸發(fā)力九。
  • DOMCharacterDataModified:在文本節(jié)點(diǎn)的值發(fā)生變化時(shí)觸發(fā)耍铜。

??使用下列代碼可以檢測(cè)出瀏覽器是否支持變動(dòng)事件:

var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");

??IE8 及更早版本不支持任何變動(dòng)事件。下表列出了不同瀏覽器對(duì)不同變動(dòng)事件的支持情況跌前。

事 件 Opera 9+ Firefox 3+ Safari 3+及Chrome IE9+
DOMSubtreeModified 支持 支持 支持
DOMNodeInserted 支持 支持 支持 支持
DOMNodeRemoved 支持 支持 支持 支持

??由于 DOM3 級(jí)事件模塊作廢了很多變動(dòng)事件棕兼,所以本節(jié)只介紹那些將來(lái)仍然會(huì)得到支持的事件。

1. 刪除節(jié)點(diǎn)

??在使用 removeChild() 或 replaceChild() 從 DOM 中刪除節(jié)點(diǎn)時(shí)舒萎,首先會(huì)觸發(fā) DOMNodeRemoved 事件程储。這個(gè)事件的目標(biāo)(event.target)是被刪除的節(jié)點(diǎn),而 event.relatedNode 屬性中包含著對(duì)目標(biāo)節(jié)點(diǎn)父節(jié)點(diǎn)的引用臂寝。在這個(gè)事件觸發(fā)時(shí),節(jié)點(diǎn)尚未從其父節(jié)點(diǎn)刪除摊灭,因此其 parentNode 屬性仍然指向父節(jié)點(diǎn)(與 event.relatedNode 相同)咆贬。這個(gè)事件會(huì)冒泡,因而可以在 DOM 的任何層次上面處理它帚呼。
??如果被移除的節(jié)點(diǎn)包含子節(jié)點(diǎn)掏缎,那么在其所有子節(jié)點(diǎn)以及這個(gè)被移除的節(jié)點(diǎn)上會(huì)相繼觸發(fā) DOMNodeRemovedFromDocument 事件。但這個(gè)事件不會(huì)冒泡煤杀,所以只有直接指定給其中一個(gè)子節(jié)點(diǎn)的事件處理程序才會(huì)被調(diào)用眷蜈。這個(gè)事件的目標(biāo)是相應(yīng)的子節(jié)點(diǎn)或者那個(gè)被移除的節(jié)點(diǎn),除此之外 event 對(duì)象中不包含其他信息沈自。
??緊隨其后觸發(fā)的是 DOMSubtreeModified 事件酌儒。這個(gè)事件的目標(biāo)是被移除節(jié)點(diǎn)的父節(jié)點(diǎn);此時(shí)的 event 對(duì)象也不會(huì)提供與事件相關(guān)的其他信息枯途。
??為了理解上述事件的觸發(fā)過程忌怎,下面我們就以一個(gè)簡(jiǎn)單的 HTML 頁(yè)面為例。

<! DOCTYPE html>
<html>
<head>
    <title>Node Removal Events Example</title>
</head>
<body>
    <ul id="myList">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
</body>
</html>

??在上述例子中酪夷,我們假設(shè)要移除<ul>元素榴啸。此時(shí),就會(huì)依次觸發(fā)以下事件晚岭。
????(1) 在<ul>元素上觸發(fā) DOMNodeRemoved 事件鸥印。relatedNode 屬性等于 document.body。
????(2) 在<ul>元素上觸發(fā) DOMNodeRemovedFromDocument 事件坦报。
????(3) 在身為<ul>元素子節(jié)點(diǎn)的每個(gè)<li>元素及文本節(jié)點(diǎn)上觸發(fā) DOMNodeRemovedFromDocument 事件库说。
????(4) 在 document.body 上觸發(fā) DOMSubtreeModified 事件,因?yàn)?lt;ul>元素是 document.body 的直接子元素燎竖。

??運(yùn)行下列代碼可以驗(yàn)證以上事件發(fā)生的順序璃弄。

EventUtil.addHandler(window, "load", function(event){
    var list = document.getElementById("myList");

    EventUtil.addHandler(document, "DOMSubtreeModified", function(event){
        alert(event.type);
        alert(event.target);
    });
    EventUtil.addHandler(document, "DOMNodeRemoved", function(event){
        alert(event.type);
        alert(event.target);
        alert(event.relatedNode);
    });
    EventUtil.addHandler(list.firstChild, "DOMNodeRemovedFromDocument", function(event){
        alert(event.type);
        alert(event.target);
    });

    list.parentNode.removeChild(list);
});

??上述代碼為 document 添加了針對(duì) DOMSubtreeModified 和 DOMNodeRemoved 事件的處理程序,以便在頁(yè)面上處理這些事件构回。由于 DOMNodeRemovedFromDocument 不會(huì)冒泡夏块,所以我們將針對(duì)它的
事件處理程序直接添加給了<ul>元素的第一個(gè)子節(jié)點(diǎn)(在兼容 DOM 的瀏覽器中是一個(gè)文本節(jié)點(diǎn))疏咐。在設(shè)置了以上事件處理程序后,代碼從文檔中移除了<ul>元素脐供。

2. 插入節(jié)點(diǎn)

??在使用 appendChild()浑塞、replaceChild() 或 insertBefore() 向 DOM 中插入節(jié)點(diǎn)時(shí),首先會(huì)觸發(fā) DOMNodeInserted 事件政己。這個(gè)事件的目標(biāo)是被插入的節(jié)點(diǎn)酌壕,而 event.relatedNode 屬性中包含一個(gè)對(duì)父節(jié)點(diǎn)的引用。在這個(gè)事件觸發(fā)時(shí)歇由,節(jié)點(diǎn)已經(jīng)被插入到了新的父節(jié)點(diǎn)中卵牍。這個(gè)事件是冒泡的,因此可以在 DOM 的各個(gè)層次上處理它沦泌。
??緊接著糊昙,會(huì)在新插入的節(jié)點(diǎn)上面觸發(fā) DOMNodeInsertedIntoDocument 事件。這個(gè)事件不冒泡谢谦,因此必須在插入節(jié)點(diǎn)之前為它添加這個(gè)事件處理程序释牺。這個(gè)事件的目標(biāo)是被插入的節(jié)點(diǎn),除此之外 event 對(duì)象中不包含其他信息回挽。
??最后一個(gè)觸發(fā)的事件是 DOMSubtreeModified没咙,觸發(fā)于新插入節(jié)點(diǎn)的父節(jié)點(diǎn)寒瓦。

??我們?nèi)砸郧懊娴?HTML 文檔為例派撕,可以通過下列 JavaScript 代碼來(lái)驗(yàn)證上述事件的觸發(fā)順序硬霍。

EventUtil.addHandler(window, "load", function(event){
    var list = document.getElementById("myList");
    var item = document.createElement("li");
    item.appendChild(document.createTextNode("Item 4"));

    EventUtil.addHandler(document, "DOMSubtreeModified", function(event){
        alert(event.type);
        alert(event.target);
    });
    EventUtil.addHandler(document, "DOMNodeInserted", function(event){
        alert(event.type);
        alert(event.target);
        alert(event.relatedNode);
    });
    EventUtil.addHandler(item, "DOMNodeInsertedIntoDocument", function(event){
        alert(event.type); 
       alert(event.target);
    });

    list.appendChild(item);
}); 

??上述代碼首先創(chuàng)建了一個(gè)包含文本"Item 4"的新<li>元素寒屯。由于 DOMSubtreeModified 和 DOMNodeInserted 事件是冒泡的凌摄,所以把它們的事件處理程序添加到了文檔中穴张。在將列表項(xiàng)插入到其父節(jié)點(diǎn)之前鲁猩,先將 DOMNodeInsertedIntoDocument 事件的事件處理程序添加給它腾供。最后一步就是使用 appendChild() 來(lái)添加這個(gè)列表項(xiàng)憔古;此時(shí)遮怜,事件開始依次被觸發(fā)。
??首先是在新<li>元素項(xiàng)上觸發(fā) DOMNodeInserted 事件鸿市,其 relatedNode 是<ul>元素锯梁。然后是觸發(fā)新<li>元素上的 DOMNodeInsertedIntoDocument 事件,最后觸發(fā)的是<ul>元素上的 DOMSubtreeModified 事件焰情。

4.7陌凳、HTML5 事件

??DOM 規(guī)范沒有涵蓋所有瀏覽器支持的所有事件。很多瀏覽器出于不同的目的——滿足用戶需求或解決特殊問題内舟,還實(shí)現(xiàn)了一些自定義的事件合敦。
??HTML5 詳盡列出了瀏覽器應(yīng)該支持的所有事件。本節(jié)只討論其中得到瀏覽器完善支持的事件验游,但并非所有事件充岛。

1. contextmenu 事件

??Windows 95 在 PC 中引入了上下文菜單的概念保檐,即通過單擊鼠標(biāo)右鍵可以調(diào)出上下文菜單。不久崔梗, 這個(gè)概念也被引入了 Web 領(lǐng)域夜只。
??為了實(shí)現(xiàn)上下文菜單,開發(fā)人員面臨的主要問題是如何確定應(yīng)該顯示上下文菜單(在 Windows 中蒜魄,是右鍵單擊扔亥;在 Mac 中,是 Ctrl+單擊)谈为,以及如何屏蔽與該操作關(guān)聯(lián)的默認(rèn)上下文菜單旅挤。
??為解決這個(gè)問題,就出現(xiàn)了 contextmenu 這個(gè)事件伞鲫,用以表示何時(shí)應(yīng)該顯示上下文菜單谦铃,以便開發(fā)人員取消默認(rèn)的上下文菜單而提供自定義的菜單。
??由于 contextmenu 事件是冒泡的榔昔,因此可以為 document 指定一個(gè)事件處理程序,用以處理頁(yè)面中發(fā)生的所有此類事件瘪菌。
??這個(gè)事件的目標(biāo)是發(fā)生用戶操作的元素撒会。在所有瀏覽器中都可以取消這個(gè)事件: 在兼容 DOM 的瀏覽器中,使用 event.preventDefalut()师妙;在 IE 中诵肛,將 event.returnValue 的值設(shè)置為 false。
??因?yàn)?contextmenu 事件屬于鼠標(biāo)事件默穴,所以其事件對(duì)象中包含與光標(biāo)位置有關(guān)的所有屬性怔檩。通常使用 contextmenu 事件來(lái)顯示自定義的上下文菜單,而使用 onclick 事件處理程序來(lái)隱 藏該菜單蓄诽。以下面的 HTML 頁(yè)面為例薛训。

<!DOCTYPE html>
<html>
<head>
    <title>ContextMenu Event Example</title>
</head>
<body> 
    <div id="myDiv">Right click or Ctrl+click me to get a custom context menu. Click anywhere else to get the default context menu.</div> 
    <ul id="myMenu" style="position:absolute;visibility:hidden;background-color: silver"> 
        <li><a >Nicholas’ site</a></li> 
        <li><a >Wrox site</a></li> 
        <li><a >Yahoo!</a></li> 
    </ul> 
</body>
</html> 

??這里的<div>元素包含一個(gè)自定義的上下文菜單。其中仑氛,<ul>元素作為自定義上下文菜單乙埃,并且在初始時(shí)是隱藏的。實(shí)現(xiàn)這個(gè)例子的 JavaScript 代碼如下所示锯岖。

EventUtil.addHandler(window, "load", function(event){
    var div = document.getElementById("myDiv");

    EventUtil.addHandler(div, "contextmenu", function(event){
        event = EventUtil.getEvent(event);
        EventUtil.preventDefault(event);

        var menu = document.getElementById("myMenu");            
        menu.style.left = event.clientX + "px";
        menu.style.top = event.clientY + "px";
        menu.style.visibility = "visible";
    });

    EventUtil.addHandler(document, "click", function(event){
        document.getElementById("myMenu").style.visibility = "hidden";
    });
});

??在上述例子中介袜,我們?yōu)?lt;div>元素添加了 oncontextmenu 事件的處理程序。
??這個(gè)事件處理程序首先會(huì)取消默認(rèn)行為出吹,以保證不顯示瀏覽器默認(rèn)的上下文菜單遇伞。
??然后,再根據(jù) event 對(duì)象 clientX 和 clientY 屬性的值捶牢,來(lái)確定放置<ul>元素的位置鸠珠。
??最后一步就是通過將 visibility 屬性設(shè)置為"visible"來(lái)顯示自定義上下文菜單巍耗。
??另外,還為 document 添加了一個(gè) onclick 事件處理程序跳芳,以便用戶能夠通過鼠標(biāo)單擊來(lái)隱藏菜單(單擊也是隱藏系統(tǒng)上下文菜單的默認(rèn)操作)芍锦。

??雖然這個(gè)例子很簡(jiǎn)單,但它卻展示了 Web 上所有自定義上下文菜單的基本結(jié)構(gòu)飞盆。只需為這個(gè)例子中的上下文菜單添加一些 CSS 樣式娄琉,就可以得到非常棒的效果。
??支持 contextmenu 事件的瀏覽器有 IE吓歇、Firefox孽水、Safari、Chrome 和 Opera 11+城看。

2. beforeunload 事件

??之所以有發(fā)生在 window 對(duì)象上的 beforeunload 事件女气,是為了讓開發(fā)人員有可能在頁(yè)面卸載前阻止這一操作。
??這個(gè)事件會(huì)在瀏覽器卸載頁(yè)面之前觸發(fā)测柠,可以通過它來(lái)取消卸載并繼續(xù)使用原有頁(yè)面炼鞠。
??但是,不能徹底取消這個(gè)事件轰胁,因?yàn)槟蔷拖喈?dāng)于讓用戶無(wú)法離開當(dāng)前頁(yè)面了谒主。為此,這個(gè)事件的意圖是將控制權(quán)交給用戶赃阀。顯示的消息會(huì)告知用戶頁(yè)面行將被卸載(正因?yàn)槿绱瞬艜?huì)顯示這個(gè)消息)霎肯,詢問用戶是否真的要關(guān)閉頁(yè)面,還是希望繼續(xù)留下來(lái)榛斯。
??為了顯示這個(gè)彈出對(duì)話框观游,必須將 event.returnValue 的值設(shè)置為要顯示給用戶的字符串(對(duì) IE 及 Fiefox 而言),同時(shí)作為函數(shù)的值返回(對(duì) Safari 和 Chrome 而言)驮俗,如下面的例子所示懂缕。

EventUtil.addHandler(window, "beforeunload", function(event){
    event = EventUtil.getEvent(event);
    var message = "I'm really going to miss you if you go.";
    event.returnValue = message;
    return message;
});

??IE 和 Firefox、Safari 和 Chrome 都支持 beforeunload 事件意述,也都會(huì)彈出這個(gè)對(duì)話框詢問用戶是否真想離開提佣。Opera 11 及之前的版本不支持 beforeunload 事件。

3. DOMContentLoaded 事件

??如前所述荤崇,window 的 load 事件會(huì)在頁(yè)面中的一切都加載完畢時(shí)觸發(fā)拌屏,但這個(gè)過程可能會(huì)因?yàn)橐虞d的外部資源過多而頗費(fèi)周折。
??而 DOMContentLoaded 事件則在形成完整的 DOM 樹之后就會(huì)觸發(fā)术荤,不理會(huì)圖像倚喂、JavaScript 文件、CSS 文件或其他資源是否已經(jīng)下載完畢。
??與 load 事件不同端圈,DOMContentLoaded 支持在頁(yè)面下載的早期添加事件處理程序焦读,這也就意味著用戶能夠盡早地與頁(yè)面進(jìn)行交互。
??要處理 DOMContentLoaded 事件舱权,可以為 document 或 window 添加相應(yīng)的事件處理程序(盡管這個(gè)事件會(huì)冒泡到 window矗晃,但它的目標(biāo)實(shí)際上是 document)。來(lái)看下面的例子宴倍。

EventUtil.addHandler(document, "DOMContentLoaded", function(event){
    alert("Content loaded");
});

??DOMContentLoaded 事件對(duì)象不會(huì)提供任何額外的信息(其 target 屬性是 document)张症。
??IE9+、Firefox鸵贬、Chrome俗他、Safari 3.1+和 Opera 9+都支持 DOMContentLoaded 事件,通常這個(gè)事件既可以添加事件處理程序阔逼,也可以執(zhí)行其他 DOM 操作兆衅。這個(gè)事件始終都會(huì)在 load 事件之前觸發(fā)。
??對(duì)于不支持 DOMContentLoaded 的瀏覽器嗜浮,我們建議在頁(yè)面加載期間設(shè)置一個(gè)時(shí)間為 0 毫秒的超時(shí)調(diào)用羡亩,如下面的例子所示。

setTimeout(function(){
   // 在此添加事件處理程序
}, 0);

??這段代碼的實(shí)際意思就是:“在當(dāng)前 JavaScript 處理完成后立即運(yùn)行這個(gè)函數(shù)危融∠Υ海”在頁(yè)面下載和構(gòu)建期間,只有一個(gè) JavaScript 處理過程专挪,因此超時(shí)調(diào)用會(huì)在該過程結(jié)束時(shí)立即觸發(fā)。至于這個(gè)時(shí)間與 DOMContentLoaded 被觸發(fā)的時(shí)間能否同步片排,主要還是取決于用戶使用的瀏覽器和頁(yè)面中的其他代碼寨腔。
??為了確保這個(gè)方法有效,必須將其作為頁(yè)面中的第一個(gè)超時(shí)調(diào)用率寡;即便如此迫卢,也還是無(wú)法保證在所有環(huán)境中該超時(shí)調(diào)用一定會(huì)早于 load 事件被觸發(fā)。

4. readystatechange 事件

??IE 為 DOM 文檔中的某些部分提供了 readystatechange 事件冶共。這個(gè)事件的目的是提供與文檔或元素的加載狀態(tài)有關(guān)的信息乾蛤,但這個(gè)事件的行為有時(shí)候也很難預(yù)料。支持 readystatechange 事件的每個(gè)對(duì)象都有一個(gè) readyState 屬性捅僵,可能包含下列 5 個(gè)值中的一個(gè)家卖。

  • uninitialized(未初始化):對(duì)象存在但尚未初始化。
  • loading(正在加載):對(duì)象正在加載數(shù)據(jù)庙楚。
  • loaded(加載完畢):對(duì)象加載數(shù)據(jù)完成上荡。
  • interactive(交互):可以操作對(duì)象了,但還沒有完全加載馒闷。
  • complete(完成):對(duì)象已經(jīng)加載完畢酪捡。

??這些狀態(tài)看起來(lái)很直觀叁征,但并非所有對(duì)象都會(huì)經(jīng)歷 readyState 的這幾個(gè)階段。換句話說逛薇,如果某個(gè)階段不適用某個(gè)對(duì)象捺疼,則該對(duì)象完全可能跳過該階段;并沒有規(guī)定哪個(gè)階段適用于哪個(gè)對(duì)象永罚。顯然啤呼,這意味著 readystatechange 事件經(jīng)常會(huì)少于 4 次,而 readyState 屬性的值也不總是連續(xù)的尤蛮。
??對(duì)于 document 而言媳友,值為"interactive"的 readyState 會(huì)在與 DOMContentLoaded 大致相同的時(shí)刻觸發(fā) readystatechange 事件。此時(shí)产捞,DOM 樹已經(jīng)加載完畢醇锚,可以安全地操作它了,因此就會(huì)進(jìn)入交互(interactive)階段坯临。但與此同時(shí)焊唬,圖像及其他外部文件不一定可用。下面來(lái)看一段處理 readystatechange 事件的代碼看靠。

EventUtil.addHandler(document, "readystatechange", function(event){
    if (document.readyState == "interactive"){
        alert("Content loaded");
    }
});

??這個(gè)事件的 event 對(duì)象不會(huì)提供任何信息赶促,也沒有目標(biāo)對(duì)象。
??在與 load 事件一起使用時(shí)挟炬,無(wú)法預(yù)測(cè)兩個(gè)事件觸發(fā)的先后順序鸥滨。在包含較多或較大的外部資源的頁(yè)面中,會(huì)在 load 事件觸發(fā)之前先進(jìn)入交互階段谤祖;而在包含較少或較小的外部資源的頁(yè)面中婿滓,則很難說 readystatechange 事件會(huì)發(fā)生在 load 事件前面。
??讓問題變得更復(fù)雜的是粥喜,交互階段可能會(huì)早于也可能會(huì)晚于完成階段出現(xiàn)凸主,無(wú)法確保順序。在包含較多外部資源的頁(yè)面中额湘,交互階段更有可能早于完成階段出現(xiàn)卿吐;而在頁(yè)面中包含較少外部資源的情況下,完成階段先于交互階段出現(xiàn)的可能性更大锋华。因此嗡官,為了盡可能搶到先機(jī),有必要同時(shí)檢測(cè)交互和完成階段毯焕,如下面的例子所示谨湘。

EventUtil.addHandler(document, "readystatechange", function(event){
    if (document.readyState == "interactive" || document.readyState == "complete"){
        EventUtil.removeHandler(document, "readystatechange", arguments.callee);
        alert("Content loaded");
    }
});

??對(duì)于上面的代碼來(lái)說,當(dāng) readystatechange 事件觸發(fā)時(shí),會(huì)檢測(cè) document.readyState 的值紧阔,看當(dāng)前是否已經(jīng)進(jìn)入交互階段或完成階段坊罢。如果是,則移除相應(yīng)的事件處理程序以免在其他階段再執(zhí)行擅耽。
注意活孩,由于事件處理程序使用的是匿名函數(shù),因此這里使用了 arguments.callee 來(lái)引用該函數(shù)乖仇。然后憾儒,會(huì)顯示一個(gè)警告框,說明內(nèi)容已經(jīng)加載完畢乃沙。這樣編寫代碼可以達(dá)到與使用 DOMContentLoaded 十分相近的效果起趾。
??支持 readystatechange 事件的瀏覽器有 IE、Firfox 4+和 Opera警儒。

??雖然使用 readystatechange 可以十分近似地模擬 DOMContentLoaded 事件训裆,但它們本質(zhì)上還是不同的。在不同頁(yè)面中蜀铲,load 事件與 readystatechange 事件并不能保證以相同的順序觸發(fā)边琉。

??另外, <script>(在 IE 和 Opera 中)和<link>(僅 IE 中)元素也會(huì)觸發(fā) readystatechange 事件记劝,可以用來(lái)確定外部的 JavaScript 和 CSS 文件是否已經(jīng)加載完成变姨。
??與在其他瀏覽器中一樣,除非把動(dòng)態(tài)創(chuàng)建的元素添加到頁(yè)面中厌丑,否則瀏覽器不會(huì)開始下載外部資源定欧。
??基于元素觸發(fā)的 readystatechange 事件也存在同樣的問題,即 readyState 屬性無(wú)論等于 "loaded" 還是"complete"都可以表示資源已經(jīng)可用怒竿。
??有時(shí)候忧额,readyState 會(huì)停在"loaded"階段而永遠(yuǎn)不會(huì)“完成”掰邢;有時(shí)候电爹,又會(huì)跳過"loaded"階段而直接“完成”气嫁。于是,還需要像對(duì)待 document 一樣采取相同的編碼方式耍属。例如,下面展示了一段加載外部 JavaScript 文件的代碼巩检。

EventUtil.addHandler(window, "load", function(){
    var script = document.createElement("script");

    EventUtil.addHandler(script, "readystatechange", function(event){
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);

        if (target.readyState == "loaded" || target.readyState == "complete"){
            EventUtil.removeHandler(target, "readystatechange", arguments.callee);
            alert("Script Loaded");
        }
    });
    script.src = "example.js";
    document.body.appendChild(script);
});

??上述例子為新創(chuàng)建的<script>節(jié)點(diǎn)指定了一個(gè)事件處理程序厚骗。事件的目標(biāo)是該節(jié)點(diǎn)本身,因此當(dāng)觸發(fā) readystatechange 事件時(shí)兢哭,要檢測(cè)目標(biāo)的 readyState 屬性是不是等于"loaded"或"complete"领舰。如果進(jìn)入了其中任何一個(gè)階段,則移除事件處理程序(以防止被執(zhí)行兩次),并顯示一個(gè)警告框冲秽。與此同時(shí)舍咖,就可以執(zhí)行已經(jīng)加載完畢的外部文件中的函數(shù)了。
??同樣的編碼方式也適用于通過<link>元素加載 CSS 文件的情況锉桑,如下面的例子所示排霉。

EventUtil.addHandler(window, "load", function(){
    var link = document.createElement("link");
    link.type = "text/css";
    link.rel= "stylesheet";

    EventUtil.addHandler(script, "readystatechange", function(event){
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);

        if (target.readyState == "loaded" || target.readyState == "complete"){
            EventUtil.removeHandler(target, "readystatechange", arguments.callee);
            alert("CSS Loaded");
        }
    });
    link.href = "example.css";
    document.getElementsByTagName("head")[0].appendChild(link);
}); 

??同樣,最重要的是要一并檢測(cè) readyState 的兩個(gè)狀態(tài)民轴,并在調(diào)用了一次事件處理程序后就將其移除攻柠。

5. pageshow 和 pagehide 事件

??Firefox 和 Opera 有一個(gè)特性,名叫“往返緩存”(back-forward cache后裸,或 bfcache)瑰钮,可以在用戶使用瀏覽器的“后退”和“前進(jìn)”按鈕時(shí)加快頁(yè)面的轉(zhuǎn)換速度。
??這個(gè)緩存中不僅保存著頁(yè)面數(shù)據(jù)微驶,還保存了 DOM 和 JavaScript 的狀態(tài)浪谴;實(shí)際上是將整個(gè)頁(yè)面都保存在了內(nèi)存里。
??如果頁(yè)面位于 bfcache 中祈搜,那么再次打開該頁(yè)面時(shí)就不會(huì)觸發(fā) load 事件较店。盡管由于內(nèi)存中保存了整個(gè)頁(yè)面的狀態(tài),不觸發(fā) load 事件也不應(yīng)該會(huì)導(dǎo)致什么問題容燕,但為了更形象地說明 bfcache 的行為梁呈,F(xiàn)irefox 還是提供了一些新事件。

??第一個(gè)事件就是 pageshow蘸秘,這個(gè)事件在頁(yè)面顯示時(shí)觸發(fā)官卡,無(wú)論該頁(yè)面是否來(lái)自 bfcache。
??在重新加載的頁(yè)面中醋虏,pageshow 會(huì)在 load 事件觸發(fā)后觸發(fā)寻咒;而對(duì)于 bfcache 中的頁(yè)面,pageshow 會(huì)在頁(yè)面狀態(tài)完全恢復(fù)的那一刻觸發(fā)颈嚼。
??另外要注意的是毛秘,雖然這個(gè)事件的目標(biāo)是 document,但必須將其事件處理程序添加到 window阻课。來(lái)看下面的例子叫挟。

(function(){
    var showCount = 0;

    EventUtil.addHandler(window, "load", function(){
        alert("Load fired");
    });

    EventUtil.addHandler(window, "pageshow", function(){
        showCount++;
        alert("Show has been fired " + showCount + " times.");
    });
})();

??上述例子使用了私有作用域,以防止變量 showCount 進(jìn)入全局作用域限煞。當(dāng)頁(yè)面首次加載完成時(shí)抹恳,showCount 的值為 0。此后署驻,每當(dāng)觸發(fā) pageshow 事件奋献,showCount 的值就會(huì)遞增并通過警告框顯示出來(lái)健霹。
??如果你在離開包含以上代碼的頁(yè)面之后,又單擊“后退”按鈕返回該頁(yè)面瓶蚂,就會(huì)看到 showCount 每次遞增的值糖埋。這是因?yàn)樵撟兞康臓顟B(tài),乃至整個(gè)頁(yè)面的狀態(tài)扬跋,都被保存在了內(nèi)存中阶捆,當(dāng)你返回這個(gè)頁(yè)面時(shí),它們的狀態(tài)得到了恢復(fù)钦听。如果你單擊了瀏覽器的“刷新”按鈕洒试,那么 showCount 的值就會(huì)被重置為 0,因?yàn)轫?yè)面已經(jīng)完全重新加載了朴上。

??除了通常的屬性之外垒棋,pageshow 事件的 event 對(duì)象還包含一個(gè)名為 persisted 的布爾值屬性。
??如果頁(yè)面被保存在了 bfcache 中痪宰,則這個(gè)屬性的值為 true叼架;否則,這個(gè)屬性的值為 false衣撬」远可以像下面這樣在事件處理程序中檢測(cè)這個(gè)屬性。

(function(){
    var showCount = 0;
    EventUtil.addHandler(window, "load", function(){
        alert("Load fired");
    });
    EventUtil.addHandler(window, "pageshow", function(){
        showCount++;
        alert("Show has been fired " + showCount + " times. Persisted? " + event.persisted);
    });
})(); 

??通過檢測(cè) persisted 屬性具练,就可以根據(jù)頁(yè)面在 bfcache 中的狀態(tài)來(lái)確定是否需要采取其他操作乍构。

??與 pageshow 事件對(duì)應(yīng)的是 pagehide 事件,該事件會(huì)在瀏覽器卸載頁(yè)面的時(shí)候觸發(fā)扛点,而且是在 unload 事件之前觸發(fā)哥遮。
??與 pageshow 事件一樣,pagehide 在 document 上面觸發(fā)陵究,但其事件處理程序必須要添加到 window 對(duì)象眠饮。
??這個(gè)事件的 event 對(duì)象也包含 persisted 屬性,不過其用途稍有不同铜邮。來(lái)看下面的例子仪召。

EventUtil.addHandler(window, "pagehide", function(event){
    alert("Hiding. Persisted? " + event.persisted);
});

??有時(shí)候,可能需要在 pagehide 事件觸發(fā)時(shí)根據(jù) persisted 的值采取不同的操作松蒜。
??對(duì)于 pageshow 事件扔茅,如果頁(yè)面是從 bfcache 中加載的,那么 persisted 的值就是 true牍鞠;對(duì)于 pagehide 事件,如果頁(yè)面在卸載之后會(huì)被保存在 bfcache 中评姨,那么 persisted 的值也會(huì)被設(shè)置為 true难述。
??因此萤晴,當(dāng)?shù)谝淮斡|發(fā) pageshow 時(shí),persisted 的值一定是 false胁后,而在第一次觸發(fā) pagehide 時(shí)店读,persisted 就會(huì)變成 true(除非頁(yè)面不會(huì)被保存在 bfcache 中)。

??支持 pageshow 和 pagehide 事件的瀏覽器有 Firefox攀芯、Safari 5+屯断、Chrome 和 Opera。IE9 及之前版本不支持這兩個(gè)事件侣诺。

??指定了 onunload 事件處理程序的頁(yè)面會(huì)被自動(dòng)排除在 bfcache 之外殖演,即使事件處理程序是空的。原因在于年鸳,onunload 最常用于撤銷在 onload 中所執(zhí)行的操作趴久,而跳過 onload 后再次顯示頁(yè)面很可能就會(huì)導(dǎo)致頁(yè)面不正常。

6. hashchange 事件

??HTML5 新增了 hashchange 事件搔确,以便在 URL 的參數(shù)列表(及 URL 中“#”號(hào)后面的所有字符串)發(fā)生變化時(shí)通知開發(fā)人員彼棍。
??之所以新增這個(gè)事件,是因?yàn)樵?Ajax 應(yīng)用中膳算,開發(fā)人員經(jīng)常要利用 URL 參數(shù)列表來(lái)保存狀態(tài)或?qū)Ш叫畔ⅰ?br> ??必須要把 hashchange 事件處理程序添加給 window 對(duì)象座硕,然后 URL 參數(shù)列表只要變化就會(huì)調(diào)用它。此時(shí)的 event 對(duì)象應(yīng)該額外包含兩個(gè)屬性:oldURL 和 newURL涕蜂。這兩個(gè)屬性分別保存著參數(shù)列表變化前后的完整 URL华匾。例如:

EventUtil.addHandler(window, "hashchange", function(event){
    alert("Old URL: " + event.oldURL + "\nNew URL: " + event.newURL);
});

??支持 hashchange 事件的瀏覽器有 IE8+、Firefox 3.6+宇葱、Safari 5+瘦真、Chrome 和 Opera 10.6+。在這些瀏覽器中黍瞧,只有 Firefox 6+诸尽、Chrome 和 Opera 支持 oldURL 和 newURL 屬性。為此印颤,最好是使用 location 對(duì)象來(lái)確定當(dāng)前的參數(shù)列表您机。

EventUtil.addHandler(window, "hashchange", function(event){
 alert("Current hash: " + location.hash);
}); 

??使用以下代碼可以檢測(cè)瀏覽器是否支持 hashchange 事件:

var isSupported = ("onhashchange" in window); // 這里有 bug

??如果 IE8 是在 IE7 文檔模式下運(yùn)行,即使功能無(wú)效它也會(huì)返回 true年局。為解決這個(gè)問題际看,可以使用以下這個(gè)更穩(wěn)妥的檢測(cè)方式:

var isSupported = ("onhashchange" in window) && (document.documentMode ===
 undefined || document.documentMode > 7);

4.8、 設(shè)備事件

??智能手機(jī)和平板電腦的普及矢否,為用戶與瀏覽器交互引入了一種新的方式仲闽,而一類新事件也應(yīng)運(yùn)而生。

??設(shè)備事件(device event)可以讓開發(fā)人員確定用戶在怎樣使用設(shè)備僵朗。

??W3C 從 2011 年開始著手制定一份關(guān)于設(shè)備事件的新草案(http://dev.w3.org/geo/api/spec-source-orientation.html)赖欣,以涵蓋不斷增長(zhǎng)的設(shè)備類型并為它們定義相關(guān)的事件屑彻。本節(jié)會(huì)同時(shí)討論這份草案中涉及的 API 和特定于瀏覽器開發(fā)商的事件。

1. orientationchange 事件

??蘋果公司為移動(dòng) Safari 中添加了 orientationchange 事件顶吮,以便開發(fā)人員能夠確定用戶何時(shí)將設(shè)備由橫向查看模式切換為縱向查看模式社牲。

??移動(dòng) Safari 的 window.orientation 屬性中可能包含 3 個(gè)值:
0 表示肖像模式,90 表示向左旋轉(zhuǎn)的橫向模式(“主屏幕”按鈕在右側(cè))悴了,-90 表示向右旋轉(zhuǎn)的橫向模式(“主屏幕”按鈕在左側(cè))搏恤。

??相關(guān)文檔中還提到一個(gè)值,即 180 表示 iPhone 頭朝下湃交;但這種模式至今尚未得到支持熟空。

??只要用戶改變了設(shè)備的查看模式,就會(huì)觸發(fā) orientationchange 事件巡揍。此時(shí)的 event 對(duì)象不包含任何有價(jià)值的信息痛阻,因?yàn)槲ㄒ幌嚓P(guān)的信息可以通過 window.orientation 訪問到。下面是使用這個(gè)事件的典型示例腮敌。

EventUtil.addHandler(window, "load", function(event){
    var div = document.getElementById("myDiv");
    div.innerHTML = "Current orientation is " + window.orientation;

    EventUtil.addHandler(window, "orientationchange", function(event){
        div.innerHTML = "Current orientation is " + window.orientation;
    });
});

??在上述例子中阱当,當(dāng)觸發(fā) load 事件時(shí)會(huì)顯示最初的方向信息。然后糜工,添加了處理 orientationchange 事件的處理程序弊添。只要發(fā)生這個(gè)事件,就會(huì)有表示新方向的信息更新頁(yè)面中的消息捌木。

??所有 iOS 設(shè)備都支持 orientationchange 事件和 window.orientation 屬性油坝。

??由于可以將 orientationchange 看成 window 事件,所以也可以通過指定
<body>元素的 onorientationchange 特性來(lái)指定事件處理程序刨裆。

2. MozOrientation 事件

??Firefox 3.6 為檢測(cè)設(shè)備的方向引入了一個(gè)名為 MozOrientation 的新事件澈圈。(前綴 Moz 表示這是特定于瀏覽器開發(fā)商的事件,不是標(biāo)準(zhǔn)事件帆啃。)當(dāng)設(shè)備的加速計(jì)檢測(cè)到設(shè)備方向改變時(shí)瞬女,就會(huì)觸發(fā)這個(gè)事件。
??但這個(gè)事件與 iOS 中的 orientationchange 事件不同努潘,該事件只能提供一個(gè)平面的方向變化诽偷。由于 MozOrientation 事件是在 window 對(duì)象上觸發(fā)的,所以可以使用以下代碼來(lái)處理疯坤。

EventUtil.addHandler(window, "MozOrientation", function(event){
    // 響應(yīng)事件
});

??此時(shí)的 event 對(duì)象包含三個(gè)屬性:x报慕、y 和 z。這幾個(gè)屬性的值都介于 1 到-1 之間压怠,表示不同坐標(biāo)軸上的方向眠冈。
??在靜止?fàn)顟B(tài)下,x 值為 0菌瘫,y 值為 0蜗顽,z 值為 1(表示設(shè)備處于豎直狀態(tài))玄柠。如果設(shè)備向右傾斜,x 值會(huì)減薪刖恕;反之宫患,向左傾斜刊懈,x 值會(huì)增大。類似地娃闲,如果設(shè)備向遠(yuǎn)離用戶的方向傾斜虚汛,y 值會(huì)減小,向接近用戶的方向傾斜皇帮,y 值會(huì)增大卷哩。z 軸檢測(cè)垂直加速度度,1 表示靜止不動(dòng)属拾,在設(shè)備移動(dòng)時(shí)值會(huì)減小将谊。(失重狀態(tài)下值為 0。)以下是輸出這三個(gè)值的一個(gè)簡(jiǎn)單的例子渐白。

EventUtil.addHandler(window, "MozOrientation", function(event){
   var output = document.getElementById("output");
   output.innerHTML = "X=" + event.x + ", Y=" + event.y + ", Z=" + event.z +"<br>";
});

??只有帶加速計(jì)的設(shè)備才支持 MozOrientation 事件尊浓,包括 Macbook、Lenovo Thinkpad纯衍、Windows Mobile 和 Android 設(shè)備栋齿。請(qǐng)大家注意,這是一個(gè)實(shí)驗(yàn)性 API襟诸,將來(lái)可能會(huì)變(可能會(huì)被其他事件取代)瓦堵。

3. deviceorientation 事件

??本質(zhì)上,DeviceOrientation Event 規(guī)范定義的 deviceorientation 事件與 MozOrientation 事件類似歌亲。它也是在加速計(jì)檢測(cè)到設(shè)備方向變化時(shí)在 window 對(duì)象上觸發(fā)菇用,而且具有與 MozOrientation 事件相同的支持限制。不過应结,deviceorientation 事件的意圖是告訴開發(fā)人員設(shè)備在空間中朝向哪兒刨疼,而不是如何移動(dòng)。
??設(shè)備在三維空間中是靠 x鹅龄、y 和 z 軸來(lái)定位的揩慕。當(dāng)設(shè)備靜止放在水平表面上時(shí),這三個(gè)值都是 0扮休。x 軸方向是從左往右迎卤,y 軸方向是從下往上,z 軸方向是從后往前(如下圖所示)玷坠。

??觸發(fā) deviceorientation 事件時(shí)蜗搔,事件對(duì)象中包含著每個(gè)軸相對(duì)于設(shè)備靜止?fàn)顟B(tài)下發(fā)生變化的信息劲藐。事件對(duì)象包含以下 5 個(gè)屬性。

  • alpha:在圍繞 z 軸旋轉(zhuǎn)時(shí)(即左右旋轉(zhuǎn)時(shí))樟凄,y 軸的度數(shù)差聘芜;是一個(gè)介于 0 到 360 之間的浮點(diǎn)數(shù)。
  • beta:在圍繞 x 軸旋轉(zhuǎn)時(shí)(即前后旋轉(zhuǎn)時(shí))缝龄,z 軸的度數(shù)差汰现;是一個(gè)介于-180 到 180 之間的浮點(diǎn)數(shù)沿侈。
  • gamma:在圍繞 y 軸旋轉(zhuǎn)時(shí)(即扭轉(zhuǎn)設(shè)備時(shí))宴合,z 軸的度數(shù)差;是一個(gè)介于 -90 到 90 之間的浮點(diǎn)數(shù)乡数。
  • absolute:布爾值炼绘,表示設(shè)備是否返回一個(gè)絕對(duì)值嗅战。
  • compassCalibrated:布爾值,表示設(shè)備的指南針是否校準(zhǔn)過俺亮。

??下面是一個(gè)輸出 alpha驮捍、beta 和 gamma 值的例子。

EventUtil.addHandler(window, "deviceorientation", function(event){
    var output = document.getElementById("output");
    output.innerHTML = "Alpha=" + event.alpha + ", Beta=" + event.beta + ", Gamma=" + event.gamma + "<br>";
});

??通過這些信息脚曾,可以響應(yīng)設(shè)備的方向厌漂,重新排列或修改屏幕上的元素。要響應(yīng)設(shè)備方向的改變而旋轉(zhuǎn)元素斟珊,可以參考如下代碼苇倡。

EventUtil.addHandler(window, "deviceorientation", function(event){
    var arrow = document.getElementById("arrow");
    arrow.style.webkitTransform = "rotate(" + Math.round(event.alpha) + "deg)";
}); 

??上述例子只能在移動(dòng) WebKit 瀏覽器中運(yùn)行,因?yàn)樗褂昧藢S械?webkitTransform 屬性(即 CSS 標(biāo)準(zhǔn)屬性 transform 的臨時(shí)版)囤踩。
??元素“arrow”會(huì)隨著 event.alpha 值的變化而旋轉(zhuǎn)旨椒,給人一種指南針的感覺。為了保證旋轉(zhuǎn)平滑堵漱,這里的 CSS3 變換使用了舍入之后的值综慎。

4. devicemotion 事件

??DeviceOrientation Event 規(guī)范還定義了一個(gè) devicemotion 事件。這個(gè)事件是要告訴開發(fā)人員設(shè)備什么時(shí)候移動(dòng)勤庐,而不僅僅是設(shè)備方向如何改變示惊。例如,通過 devicemotion 能夠檢測(cè)到設(shè)備是不是正在往下掉愉镰,或者是不是被走著的人拿在手里米罚。
??觸發(fā) devicemotion 事件時(shí),事件對(duì)象包含以下屬性丈探。

  • acceleration:一個(gè)包含 x录择、y 和 z 屬性的對(duì)象,在不考慮重力的情況下,告訴你在每個(gè)方向上的加速度隘竭。
  • accelerationIncludingGravity:一個(gè)包含 x塘秦、y 和 z 屬性的對(duì)象,在考慮 z 軸自然重力加速度的情況下动看,告訴你在每個(gè)方向上的加速度尊剔。
  • interval:以毫秒表示的時(shí)間值,必須在另一個(gè) devicemotion 事件觸發(fā)前傳入菱皆。這個(gè)值在每個(gè)事件中應(yīng)該是一個(gè)常量赋兵。
  • rotationRate:一個(gè)包含表示方向的 alpha、beta 和 gamma 屬性的對(duì)象搔预。

??如果讀取不到 acceleration、accelerationIncludingGravity 和 rotationRate 值叶组,則它們的值為 null拯田。因此,在使用這三個(gè)屬性之前甩十,應(yīng)該先檢測(cè)確定它們的值不是 null船庇。例如:

EventUtil.addHandler(window, "devicemotion", function(event){
    var output = document.getElementById("output");
    if (event.rotationRate !== null){
        output.innerHTML += "Alpha=" + event.rotationRate.alpha + ", 
                            Beta=" +  event.rotationRate.beta + ",
                            Gamma=" +  event.rotationRate.gamma;
    }
});

??與 deviceorientation 事件類似,只有 iOS 4.2+中的 Safari侣监、Chrome 和 Android 版 WebKit 實(shí)現(xiàn)了devicemotion 事件鸭轮。

4.9、 觸摸與手勢(shì)事件

??iOS 版 Safari 為了向開發(fā)人員傳達(dá)一些特殊信息橄霉,新增了一些專有事件窃爷。因?yàn)?iOS 設(shè)備既沒有鼠標(biāo)也沒有鍵盤,所以在為移動(dòng) Safari 開發(fā)交互性網(wǎng)頁(yè)時(shí)姓蜂,常規(guī)的鼠標(biāo)和鍵盤事件根本不夠用按厘。隨著 Android 中的 WebKit 的加入,很多這樣的專有事件變成了事實(shí)標(biāo)準(zhǔn)钱慢,導(dǎo)致 W3C 開始制定 Touch Events 規(guī)范逮京。以下介紹的事件只針對(duì)觸摸設(shè)備。

1. 觸摸事件

??包含 iOS 2.0 軟件的 iPhone 3G 發(fā)布時(shí)束莫,也包含了一個(gè)新版本的 Safari 瀏覽器懒棉。這款新的移動(dòng) Safari 提供了一些與觸摸(touch)操作相關(guān)的新事件。后來(lái)览绿,Android 上的瀏覽器也實(shí)現(xiàn)了相同的事件策严。

??觸摸事件會(huì)在用戶手指放在屏幕上面時(shí)、在屏幕上滑動(dòng)時(shí)或從屏幕上移開時(shí)觸發(fā)饿敲。具體來(lái)說享钞,有以下幾個(gè)觸摸事件。

  • touchstart:當(dāng)手指觸摸屏幕時(shí)觸發(fā);即使已經(jīng)有一個(gè)手指放在了屏幕上也會(huì)觸發(fā)栗竖。
  • touchmove:當(dāng)手指在屏幕上滑動(dòng)時(shí)連續(xù)地觸發(fā)暑脆。在這個(gè)事件發(fā)生期間,調(diào)用 preventDefault() 可以阻止?jié)L動(dòng)狐肢。
  • touchend:當(dāng)手指從屏幕上移開時(shí)觸發(fā)添吗。
  • touchcancel:當(dāng)系統(tǒng)停止跟蹤觸摸時(shí)觸發(fā)。關(guān)于此事件的確切觸發(fā)時(shí)間份名,文檔中沒有明確說明碟联。

??上面這幾個(gè)事件都會(huì)冒泡,也都可以取消僵腺。雖然這些觸摸事件沒有在 DOM 規(guī)范中定義鲤孵,但它們卻是以兼容 DOM 的方式實(shí)現(xiàn)的。
??因此辰如,每個(gè)觸摸事件的 event 對(duì)象都提供了在鼠標(biāo)事件中常見的屬性:bubbles普监、cancelable、view琉兜、clientX凯正、clientY、screenX豌蟋、screenY廊散、detail、altKey梧疲、shiftKey允睹、ctrlKey 和 metaKey。

??除了常見的 DOM 屬性外幌氮,觸摸事件還包含下列三個(gè)用于跟蹤觸摸的屬性擂找。

  • touches:表示當(dāng)前跟蹤的觸摸操作的 Touch 對(duì)象的數(shù)組。
  • targetTouchs:特定于事件目標(biāo)的 Touch 對(duì)象的數(shù)組浩销。
  • changeTouches:表示自上次觸摸以來(lái)發(fā)生了什么改變的 Touch 對(duì)象的數(shù)組贯涎。

??每個(gè) Touch 對(duì)象包含下列屬性。

  • clientX:觸摸目標(biāo)在視口中的 x 坐標(biāo)慢洋。
  • clientY:觸摸目標(biāo)在視口中的 y 坐標(biāo)塘雳。
  • identifier:標(biāo)識(shí)觸摸的唯一 ID。
  • pageX:觸摸目標(biāo)在頁(yè)面中的 x 坐標(biāo)普筹。
  • pageY:觸摸目標(biāo)在頁(yè)面中的 y 坐標(biāo)败明。
  • screenX:觸摸目標(biāo)在屏幕中的 x 坐標(biāo)。
  • screenY:觸摸目標(biāo)在屏幕中的 y 坐標(biāo)太防。
  • target:觸摸的 DOM 節(jié)點(diǎn)目標(biāo)妻顶。

??使用這些屬性可以跟蹤用戶對(duì)屏幕的觸摸操作酸员。來(lái)看下面的例子。

function handleTouchEvent(event){

    // 只跟蹤一次觸摸
    if (event.touches.length == 1){
        var output = document.getElementById("output");
        switch(event.type){
            case "touchstart":
                output.innerHTML = "Touch started (" + event.touches[0].clientX + "," + event.touches[0].clientY + ")";
                break;
            case "touchend":
                output.innerHTML += "<br>Touch ended (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
                break;
            case "touchmove":
                event.preventDefault(); //阻止?jié)L動(dòng)
                output.innerHTML += "<br>Touch moved (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
                break;
        }
    }
}

EventUtil.addHandler(document, "touchstart", handleTouchEvent);
EventUtil.addHandler(document, "touchend", handleTouchEvent);
EventUtil.addHandler(document, "touchmove", handleTouchEvent);

??以上代碼會(huì)跟蹤屏幕上發(fā)生的一次觸摸操作讳嘱。為簡(jiǎn)單起見幔嗦,只會(huì)在有一次活動(dòng)觸摸操作的情況下輸出信息。
??當(dāng) touchstart 事件發(fā)生時(shí),會(huì)將觸摸的位置信息輸出到<div>元素中。
??當(dāng) touchmove 事件發(fā)生時(shí)挽鞠,會(huì)取消其默認(rèn)行為,阻止?jié)L動(dòng)(觸摸移動(dòng)的默認(rèn)行為是滾動(dòng)頁(yè)面)矛纹,然后輸出觸摸操作的變化信息。
??而 touchend 事件則會(huì)輸出有關(guān)觸摸操作的最終信息。注意,在 touchend 事件發(fā)生時(shí)因谎,touches 集合中就沒有任何 Touch 對(duì)象了,因?yàn)椴淮嬖诨顒?dòng)的觸摸操作颜懊;此時(shí)财岔,就必須轉(zhuǎn)而使用 changeTouchs 集合。
??這些事件會(huì)在文檔的所有元素上面觸發(fā)饭冬,因而可以分別操作頁(yè)面的不同部分。在觸摸屏幕上的元素時(shí)揪阶,這些事件(包括鼠標(biāo)事件)發(fā)生的順序如下:
????(1) touchstart
????(2) mouseover
????(3) mousemove(一次)
????(4) mousedown
????(5) mouseup
????(6) click
????(7) touchend

??支持觸摸事件的瀏覽器包括 iOS 版 Safari昌抠、Android 版 WebKit、bada 版 Dolfin鲁僚、OS6+中的 BlackBerry WebKit炊苫、Opera Mobile 10.1+和 LG 專有 OS 中的 Phantom 瀏覽器。目前只有 iOS 版 Safari 支持多點(diǎn)觸摸冰沙。

??桌面版 Firefox 6+和 Chrome 也支持觸摸事件侨艾。

2. 手勢(shì)事件

??iOS 2.0 中的 Safari 還引入了一組手勢(shì)事件。當(dāng)兩個(gè)手指觸摸屏幕時(shí)就會(huì)產(chǎn)生手勢(shì)拓挥,手勢(shì)通常會(huì)改變顯示項(xiàng)的大小唠梨,或者旋轉(zhuǎn)顯示項(xiàng)。有三個(gè)手勢(shì)事件侥啤,分別介紹如下当叭。

  • gesturestart:當(dāng)一個(gè)手指已經(jīng)按在屏幕上而另一個(gè)手指又觸摸屏幕時(shí)觸發(fā)。
  • gesturechange:當(dāng)觸摸屏幕的任何一個(gè)手指的位置發(fā)生變化時(shí)觸發(fā)盖灸。
  • gestureend:當(dāng)任何一個(gè)手指從屏幕上面移開時(shí)觸發(fā)蚁鳖。

??只有兩個(gè)手指都觸摸到事件的接收容器時(shí)才會(huì)觸發(fā)這些事件。在一個(gè)元素上設(shè)置事件處理程序赁炎,意味著兩個(gè)手指必須同時(shí)位于該元素的范圍之內(nèi)醉箕,才能觸發(fā)手勢(shì)事件(這個(gè)元素就是目標(biāo))。
??由于這些事件冒泡,所以將事件處理程序放在文檔上也可以處理所有手勢(shì)事件讥裤。此時(shí)放棒,事件的目標(biāo)就是兩個(gè)手指都位于其范圍內(nèi)的那個(gè)元素。

??觸摸事件和手勢(shì)事件之間存在某種關(guān)系坞琴。當(dāng)一個(gè)手指放在屏幕上時(shí)哨查,會(huì)觸發(fā) touchstart 事件。如果另一個(gè)手指又放在了屏幕上剧辐,則會(huì)先觸發(fā) gesturestart 事件寒亥,隨后觸發(fā)基于該手指的 touchstart 事件。如果一個(gè)或兩個(gè)手指在屏幕上滑動(dòng)荧关,將會(huì)觸發(fā) gesturechange 事件溉奕。但只要有一個(gè)手指移開,就會(huì)觸發(fā) gestureend 事件忍啤,緊接著又會(huì)觸發(fā)基于該手指的 touchend 事件加勤。
??與觸摸事件一樣,每個(gè)手勢(shì)事件的 event 對(duì)象都包含著標(biāo)準(zhǔn)的鼠標(biāo)事件屬性:bubbles同波、cancelable鳄梅、view、clientX未檩、clientY戴尸、screenX、screenY冤狡、detail孙蒙、altKey、shiftKey悲雳、ctrlKey 和 metaKey挎峦。此外,還包含兩個(gè)額外的屬性:rotation 和 scale合瓢。
??其中坦胶,rotation 屬性表示手指變化引起的旋轉(zhuǎn)角度,負(fù)值表示逆時(shí)針旋轉(zhuǎn)晴楔,正值表示順時(shí)針旋轉(zhuǎn)(該值從 0 開始)迁央。
??而 scale 屬性表示兩個(gè)手指間距離的變化情況(例如向內(nèi)收縮會(huì)縮短距離);這個(gè)值從 1 開始滥崩,并隨距離拉大而增長(zhǎng)岖圈,隨距離縮短而減小。
??下面是使用手勢(shì)事件的一個(gè)示例钙皮。

function handleGestureEvent(event){
    var output = document.getElementById("output");
    switch(event.type){
        case "gesturestart":
            output.innerHTML = "Gesture started (rotation=" + event.rotation + ",scale=" + event.scale + ")";
            break;
        case "gestureend":
            output.innerHTML += "<br>Gesture ended (rotation=" + event.rotation + ",scale=" + event.scale + ")";
            break;
        case "gesturechange": 
           output.innerHTML += "<br>Gesture changed (rotation=" + event.rotation + ",scale=" + event.scale + ")";
            break;
    }
}

document.addEventListener("gesturestart", handleGestureEvent, false);
document.addEventListener("gestureend", handleGestureEvent, false);
document.addEventListener("gesturechange", handleGestureEvent, false);

??與前面演示觸摸事件的例子一樣蜂科,這里的代碼只是將每個(gè)事件都關(guān)聯(lián)到同一個(gè)函數(shù)中顽决,然后通過該函數(shù)輸出每個(gè)事件的相關(guān)信息。

??觸摸事件也會(huì)返回 rotation 和 scale 屬性导匣,但這兩個(gè)屬性只會(huì)在兩個(gè)手指與屏幕保持接觸時(shí)才會(huì)發(fā)生變化才菠。一般來(lái)說,使用基于兩個(gè)手指的手勢(shì)事件贡定,要比管理觸摸事件中的所有交互要容易得多赋访。

5、內(nèi)存和性能

??由于事件處理程序可以為現(xiàn)代 Web 應(yīng)用程序提供交互能力缓待,因此許多開發(fā)人員會(huì)不分青紅皂白地向頁(yè)面中添加大量的處理程序蚓耽。在創(chuàng)建 GUI 的語(yǔ)言(如 C#)中,為 GUI 中的每個(gè)按鈕添加一個(gè) onclick
事件處理程序是司空見慣的事旋炒,而且這樣做也不會(huì)導(dǎo)致什么問題步悠。
??可是在 JavaScript 中,添加到頁(yè)面上的事件處理程序數(shù)量將直接關(guān)系到頁(yè)面的整體運(yùn)行性能瘫镇。導(dǎo)致這一問題的原因是多方面的鼎兽。
??首先,每個(gè)函數(shù)都是對(duì)象铣除,都會(huì)占用內(nèi)存谚咬;內(nèi)存中的對(duì)象越多,性能就越差尚粘。
??其次择卦,必須事先指定所有事件處理程序而導(dǎo)致的 DOM 訪問次數(shù),會(huì)延遲整個(gè)頁(yè)面的交互就緒時(shí)間背苦。
??事實(shí)上互捌,從如何利用好事件處理程序的角度出發(fā)潘明,還是有一些方法能夠提升性能的行剂。

5.1、 事件委托

??對(duì)“事件處理程序過多”問題的解決方案就是事件委托钳降。事件委托利用了事件冒泡厚宰,只指定一個(gè)事件處理程序,就可以管理某一類型的所有事件遂填。
??例如铲觉,click 事件會(huì)一直冒泡到 document 層次。也就是說吓坚,我們可以為整個(gè)頁(yè)面指定一個(gè) onclick 事件處理程序撵幽,而不必給每個(gè)可單擊的元素分別添加事件處理程序。以下面的 HTML 代碼為例礁击。

<ul id="myLinks">
    <li id="goSomewhere">Go somewhere</li>
    <li id="doSomething">Do something</li>
    <li id="sayHi">Say hi</li>
</ul>

??其中包含 3 個(gè)被單擊后會(huì)執(zhí)行操作的列表項(xiàng)盐杂。按照傳統(tǒng)的做法逗载,需要像下面這樣為它們添加 3 個(gè)事件處理程序。

var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");

EventUtil.addHandler(item1, "click", function(event){
    location.;
});
EventUtil.addHandler(item2, "click", function(event){
    document.title = "I changed the document's title";
});
EventUtil.addHandler(item3, "click", function(event){
    alert("hi");
});

??如果在一個(gè)復(fù)雜的 Web 應(yīng)用程序中链烈,對(duì)所有可單擊的元素都采用這種方式厉斟,那么結(jié)果就會(huì)有數(shù)不清的代碼用于添加事件處理程序。
??此時(shí)强衡,可以利用事件委托技術(shù)解決這個(gè)問題擦秽。使用事件委托沃暗,只需在 DOM 樹中盡量最高的層次上添加一個(gè)事件處理程序谓松,如下面的例子所示。

var list = document.getElementById("myLinks");

EventUtil.addHandler(list, "click", function(event){
    event = EventUtil.getEvent(event);

    var target = EventUtil.getTarget(event);
    switch(target.id){
        case "doSomething":
            document.title = "I changed the document's title";
            break;
        case "goSomewhere":
            location.;
            break;
        case "sayHi":
            alert("hi");
            break;
    }
});

??在上述代碼里冻辩,我們使用事件委托只為<ul>元素添加了一個(gè) onclick 事件處理程序锯七。由于所有列表項(xiàng)都是這個(gè)元素的子節(jié)點(diǎn)链快,而且它們的事件會(huì)冒泡,所以單擊事件最終會(huì)被這個(gè)函數(shù)處理眉尸。
??我們知道域蜗,事件目標(biāo)是被單擊的列表項(xiàng),故而可以通過檢測(cè) id 屬性來(lái)決定采取適當(dāng)?shù)牟僮鳌?br> ??與前面未使用事件委托的代碼比一比噪猾,會(huì)發(fā)現(xiàn)這段代碼的事前消耗更低霉祸,因?yàn)橹蝗〉昧艘粋€(gè) DOM 元素,只添加了一個(gè)事件處理程序袱蜡。
??雖然對(duì)用戶來(lái)說最終的結(jié)果相同丝蹭,但這種技術(shù)需要占用的內(nèi)存更少。所有用到按鈕的事件(多數(shù)鼠標(biāo)事件和鍵盤事件)都適合采用事件委托技術(shù)坪蚁。
??如果可行的話奔穿,也可以考慮為 document 對(duì)象添加一個(gè)事件處理程序,用以處理頁(yè)面上發(fā)生的某種特定類型的事件敏晤。這樣做與采取傳統(tǒng)的做法相比具有如下優(yōu)點(diǎn)贱田。

  • document 對(duì)象很快就可以訪問,而且可以在頁(yè)面生命周期的任何時(shí)點(diǎn)上為它添加事件處理程序(無(wú)需等待 DOMContentLoaded 或 load 事件)嘴脾。換句話說男摧,只要可單擊的元素呈現(xiàn)在頁(yè)面上,就可以立即具備適當(dāng)?shù)墓δ堋?/li>
  • 在頁(yè)面中設(shè)置事件處理程序所需的時(shí)間更少译打。只添加一個(gè)事件處理程序所需的 DOM 引用更少耗拓,所花的時(shí)間也更少。
  • 整個(gè)頁(yè)面占用的內(nèi)存空間更少奏司,能夠提升整體性能乔询。

??最適合采用事件委托技術(shù)的事件包括 click、mousedown韵洋、mouseup竿刁、keydown岸夯、keyup 和 keypress。
??雖然 mouseover 和 mouseout 事件也冒泡们妥,但要適當(dāng)處理它們并不容易猜扮,而且經(jīng)常需要計(jì)算元素的位置。(因?yàn)楫?dāng)鼠標(biāo)從一個(gè)元素移到其子節(jié)點(diǎn)時(shí)监婶,或者當(dāng)鼠標(biāo)移出該元素時(shí)旅赢,都會(huì)觸發(fā) mouseout 事件。)

5.2惑惶、 移除事件處理程序

??每當(dāng)將事件處理程序指定給元素時(shí)煮盼,運(yùn)行中的瀏覽器代碼與支持頁(yè)面交互的 JavaScript 代碼之間就會(huì)建立一個(gè)連接。這種連接越多带污,頁(yè)面執(zhí)行起來(lái)就越慢僵控。如前所述,可以采用事件委托技術(shù)鱼冀,限制建立
的連接數(shù)量报破。另外,在不需要的時(shí)候移除事件處理程序千绪,也是解決這個(gè)問題的一種方案充易。
??內(nèi)存中留有那些過時(shí)不用的“空事件處理程序”(dangling event handler),也是造成 Web 應(yīng)用程序內(nèi)存與性能問題的主要原因荸型。在兩種情況下盹靴,可能會(huì)造成上述問題。
??第一種情況就是從文檔中移除帶有事件處理程序的元素時(shí)瑞妇。這可能是通過純粹的 DOM 操作稿静,例如使用 removeChild() 和 replaceChild() 方法,但更多地是發(fā)生在使用 innerHTML 替換頁(yè)面中某一部分的時(shí)候辕狰。
??如果帶有事件處理程序的元素被 innerHTML 刪除了改备,那么原來(lái)添加到元素中的事件處理程序極有可能無(wú)法被當(dāng)作垃圾回收。來(lái)看下面的例子柳琢。

<div id="myDiv">
    <input type="button" value="Click Me" id="myBtn">
</div>

<script type="text/javascript">
    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
        // 先執(zhí)行某些操作
        document.getElementById("myDiv").innerHTML = "Processing..."; // 麻煩了绍妨!
    };
</script>

??這里润脸,有一個(gè)按鈕被包含在<div>元素中柬脸。為避免雙擊,單擊這個(gè)按鈕時(shí)就將按鈕移除并替換成一條消息毙驯;這是網(wǎng)站設(shè)計(jì)中非常流行的一種做法倒堕。
??但問題在于,當(dāng)按鈕被從頁(yè)面中移除時(shí)爆价,它還帶著一個(gè)事件處理程序呢垦巴。在<div>元素上設(shè)置 innerHTML 可以把按鈕移走媳搪,但事件處理程序仍然與按鈕保持著引用關(guān)系。
??有的瀏覽器(尤其是 IE)在這種情況下不會(huì)作出恰當(dāng)?shù)靥幚碇栊鼈兒苡锌赡軙?huì)將對(duì)元素和對(duì)事件處理程序的引用都保存在內(nèi)存中秦爆。
??如果你知道某個(gè)元素即將被移除,那么最好手工移除事件處理程序憔披,如下面的例子所示等限。

<div id="myDiv">
    <input type="button" value="Click Me" id="myBtn">
</div>

<script type="text/javascript">
    var btn = document.getElementById("myBtn"); 
    btn.onclick = function(){
        // 先執(zhí)行某些操作
        btn.onclick = null; // 移除事件處理程序
        document.getElementById("myDiv").innerHTML = "Processing...";
    };
</script>

??在此,我們?cè)谠O(shè)置<div>的 innerHTML 屬性之前芬膝,先移除了按鈕的事件處理程序望门。這樣就確保了內(nèi)存可以被再次利用,而從 DOM 中移除按鈕也做到了干凈利索锰霜。
??注意筹误,在事件處理程序中刪除按鈕也能阻止事件冒泡。目標(biāo)元素在文檔中是事件冒泡的前提癣缅。
??采用事件委托也有助于解決這個(gè)問題厨剪。如果事先知道將來(lái)有可能使用innerHTML 替換掉頁(yè)面中的某一部分,那么就可以不直接把事件處理程序添加到該部分的元素中友存。而通過把事件處理程序指定給較高層次的元素丽惶,同樣能夠處理該區(qū)域中的事件。

??導(dǎo)致“空事件處理程序”的另一種情況爬立,就是卸載頁(yè)面的時(shí)候钾唬。毫不奇怪,IE8 及更早版本在這種情況下依然是問題最多的瀏覽器侠驯,盡管其他瀏覽器或多或少也有類似的問題抡秆。
??如果在頁(yè)面被卸載之前沒有清理干凈事件處理程序,那它們就會(huì)滯留在內(nèi)存中吟策。每次加載完頁(yè)面再卸載頁(yè)面時(shí)(可能是在兩個(gè)頁(yè)面間來(lái)回切換儒士,也可以是單擊了“刷新”按鈕),內(nèi)存中滯留的對(duì)象數(shù)目就會(huì)增加檩坚,因?yàn)槭录幚沓绦蛘加玫膬?nèi)存并沒有被釋放着撩。

??一般來(lái)說,最好的做法是在頁(yè)面卸載之前匾委,先通過 onunload 事件處理程序移除所有事件處理程序拖叙。在此,事件委托技術(shù)再次表現(xiàn)出它的優(yōu)勢(shì)——需要跟蹤的事件處理程序越少赂乐,移除它們就越容易薯鳍。對(duì)這種類似撤銷的操作,我們可以把它想象成:只要是通過 onload 事件處理程序添加的東西挨措,最后都要通過 onunload 事件處理程序?qū)⑺鼈円瞥?br> ??不要忘了挖滤,使用 onunload 事件處理程序意味著頁(yè)面不會(huì)被緩存在 bfcache 中崩溪。如果你在意這個(gè)問題,那么就只能在 IE 中通過 onunload 來(lái)移除事件處理程序了斩松。

6伶唯、 模擬事件

??事件,就是網(wǎng)頁(yè)中某個(gè)特別值得關(guān)注的瞬間惧盹。事件經(jīng)常由用戶操作或通過其他瀏覽器功能來(lái)觸發(fā)抵怎。
??但很少有人知道,也可以使用 JavaScript 在任意時(shí)刻來(lái)觸發(fā)特定的事件岭参,而此時(shí)的事件就如同瀏覽器創(chuàng)建的事件一樣反惕。也就是說噩凹,這些事件該冒泡還會(huì)冒泡犀暑,而且照樣能夠?qū)е聻g覽器執(zhí)行已經(jīng)指定的處理它們的事件處理程序篷角。

??在測(cè)試 Web 應(yīng)用程序记舆,模擬觸發(fā)事件是一種極其有用的技術(shù)涨薪。DOM2 級(jí)規(guī)范為此規(guī)定了模擬特定事件的方式芯肤,IE9潜支、Opera蒸矛、Firefox娄徊、Chrome 和 Safari 都支持這種方式闽颇。IE 有它自己模擬事件的方式。

6.1寄锐、 DOM 中的事件模擬

??可以在 document 對(duì)象上使用 createEvent() 方法創(chuàng)建 event 對(duì)象兵多。這個(gè)方法接收一個(gè)參數(shù),即表示要?jiǎng)?chuàng)建的事件類型的字符串橄仆。
??在 DOM2 級(jí)中剩膘,所有這些字符串都使用英文復(fù)數(shù)形式,而在 DOM3級(jí)中都變成了單數(shù)盆顾。這個(gè)字符串可以是下列幾字符串之一怠褐。

  • UIEvents:一般化的 UI 事件。鼠標(biāo)事件和鍵盤事件都繼承自 UI 事件您宪。DOM3 級(jí)中是 UIEvent奈懒。
  • MouseEvents:一般化的鼠標(biāo)事件。DOM3 級(jí)中是 MouseEvent宪巨。
  • MutationEvents:一般化的 DOM 變動(dòng)事件磷杏。DOM3 級(jí)中是 MutationEvent。
  • HTMLEvents:一般化的 HTML 事件揖铜。沒有對(duì)應(yīng)的 DOM3 級(jí)事件(HTML 事件被分散到其他類別中)茴丰。

??要注意的是达皿,“DOM2 級(jí)事件”并沒有專門規(guī)定鍵盤事件天吓,后來(lái)的“DOM3 級(jí)事件”中才正式將其作為一種事件給出規(guī)定贿肩。IE9 是目前唯一支持 DOM3 級(jí)鍵盤事件的瀏覽器。不過龄寞,在其他瀏覽器中汰规,在現(xiàn)有方法的基礎(chǔ)上,可以通過幾種方式來(lái)模擬鍵盤事件物邑。

??在創(chuàng)建了 event 對(duì)象之后溜哮,還需要使用與事件有關(guān)的信息對(duì)其進(jìn)行初始化。每種類型的 event 對(duì)象都有一個(gè)特殊的方法色解,為它傳入適當(dāng)?shù)臄?shù)據(jù)就可以初始化該 event 對(duì)象茂嗓。不同類型的這個(gè)方法的名字也不相同,具體要取決于 createEvent() 中使用的參數(shù)科阎。

??模擬事件的最后一步就是觸發(fā)事件述吸。這一步需要使用 dispatchEvent() 方法,所有支持事件的 DOM 節(jié)點(diǎn)都支持這個(gè)方法锣笨。調(diào)用 dispatchEvent() 方法時(shí)蝌矛,需要傳入一個(gè)參數(shù),即表示要觸發(fā)事件的 event 對(duì)象错英。觸發(fā)事件之后入撒,該事件就躋身“官方事件”之列了,因而能夠照樣冒泡并引發(fā)相應(yīng)事件處理程序的執(zhí)行椭岩。

1. 模擬鼠標(biāo)事件

??創(chuàng)建新的鼠標(biāo)事件對(duì)象并為其指定必要的信息茅逮,就可以模擬鼠標(biāo)事件。創(chuàng)建鼠標(biāo)事件對(duì)象的方法是為 createEvent() 傳入字符串"MouseEvents"判哥。返回的對(duì)象有一個(gè)名為 initMouseEvent() 方法氮唯,用于指定與該鼠標(biāo)事件有關(guān)的信息。這個(gè)方法接收 15 個(gè)參數(shù)姨伟,分別與鼠標(biāo)事件中每個(gè)典型的屬性一一對(duì)應(yīng)惩琉;這些參數(shù)的含義如下。

  • type(字符串):表示要觸發(fā)的事件類型夺荒,例如"click"瞒渠。
  • bubbles(布爾值):表示事件是否應(yīng)該冒泡。為精確地模擬鼠標(biāo)事件技扼,應(yīng)該把這個(gè)參數(shù)設(shè)置為 true伍玖。
  • cancelable(布爾值):表示事件是否可以取消。為精確地模擬鼠標(biāo)事件剿吻,應(yīng)該把這個(gè)參數(shù)設(shè)置為 true窍箍。
  • view(AbstractView):與事件關(guān)聯(lián)的視圖。這個(gè)參數(shù)幾乎總是要設(shè)置為 document.defaultView。
  • detail(整數(shù)):與事件有關(guān)的詳細(xì)信息椰棘。這個(gè)值一般只有事件處理程序使用纺棺,但通常都設(shè)置為 0。
  • screenX(整數(shù)):事件相對(duì)于屏幕的 X 坐標(biāo)邪狞。
  • screenY(整數(shù)):事件相對(duì)于屏幕的 Y 坐標(biāo)祷蝌。
  • clientX(整數(shù)):事件相對(duì)于視口的 X 坐標(biāo)。
  • clientY(整數(shù)):事件想對(duì)于視口的 Y 坐標(biāo)帆卓。
  • ctrlKey(布爾值):表示是否按下了 Ctrl 鍵巨朦。默認(rèn)值為 false。
  • altKey(布爾值):表示是否按下了 Alt 鍵剑令。默認(rèn)值為 false糊啡。
  • shiftKey(布爾值):表示是否按下了 Shift 鍵。默認(rèn)值為 false吁津。
  • metaKey(布爾值):表示是否按下了 Meta 鍵悔橄。默認(rèn)值為 false。
  • button(整數(shù)):表示按下了哪一個(gè)鼠標(biāo)鍵腺毫。默認(rèn)值為 0癣疟。
  • relatedTarget(對(duì)象):表示與事件相關(guān)的對(duì)象。這個(gè)參數(shù)只在模擬mouseover 或 mouseout時(shí)使用潮酒。

??顯而易見睛挚,initMouseEvent() 方法的這些參數(shù)是與鼠標(biāo)事件的 event 對(duì)象所包含的屬性一一對(duì)應(yīng)的。其中急黎,前 4 個(gè)參數(shù)對(duì)正確地激發(fā)事件至關(guān)重要扎狱,因?yàn)闉g覽器要用到這些參數(shù);而剩下的所有參數(shù)只有在事件處理程序中才會(huì)用到勃教。
??當(dāng)把 event 對(duì)象傳給 dispatchEvent() 方法時(shí)淤击,這個(gè)對(duì)象的 target 屬性會(huì)自動(dòng)設(shè)置。下面故源,我們就通過一個(gè)例子來(lái)了解如何模擬對(duì)按鈕的單擊事件污抬。

var btn = document.getElementById("myBtn");

// 創(chuàng)建事件對(duì)象
var event = document.createEvent("MouseEvents");

// 初始化事件對(duì)象
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);

// 觸發(fā)事件
btn.dispatchEvent(event);

??在兼容 DOM 的瀏覽器中,也可以通過相同的方式來(lái)模擬其他鼠標(biāo)事件(例如 dblclick)绳军。

2. 模擬鍵盤事件

??前面曾經(jīng)提到過印机,“DOM2 級(jí)事件”中沒有就鍵盤事件作出規(guī)定,因此模擬鍵盤事件并沒有現(xiàn)成的思路可循门驾。

??“DOM2 級(jí)事件”的草案中本來(lái)包含了鍵盤事件射赛,但在定稿之前又被刪除了;Firefox 根據(jù)其草案實(shí)現(xiàn)了鍵盤事件奶是。需要提請(qǐng)大家注意的是楣责,“DOM3 級(jí)事件”中的鍵盤事件與曾包含在“DOM2 級(jí)事件”草案中的鍵盤事件有很大區(qū)別竣灌。

??DOM3 級(jí)規(guī)定,調(diào)用 createEvent() 并入"KeyboardEvent"就可以創(chuàng)建一個(gè)鍵盤事件秆麸。返回的事件對(duì)象會(huì)包含一個(gè) initKeyEvent() 方法初嘹,這個(gè)方法接收下列參數(shù)。

  • type(字符串):表示要觸發(fā)的事件類型蛔屹,如"keydown"削樊。
  • bubbles(布爾值):表示事件是否應(yīng)該冒泡豁生。為精確模擬鍵盤事件兔毒,應(yīng)該設(shè)置為 true。
  • cancelable(布爾值):表示事件是否可以取消甸箱。為精確模擬鍵盤事件育叁,應(yīng)該設(shè)置為 true。
  • view(AbstractView):與事件關(guān)聯(lián)的視圖芍殖。這個(gè)參數(shù)幾乎總是要設(shè)置為 document.defaultView豪嗽。
  • key(布爾值):表示按下的鍵的鍵碼。
  • location(整數(shù)):表示按下了哪里的鍵豌骏。0 表示默認(rèn)的主鍵盤,1 表示左,2 表示右柑贞,3 表示數(shù)字鍵盤掖棉,4 表示移動(dòng)設(shè)備(即虛擬鍵盤),5 表示手柄蒂窒。
  • modifiers(字符串):空格分隔的修改鍵列表躁倒,如"Shift"。
  • repeat(整數(shù)):在一行中按了這個(gè)鍵多少次洒琢。

??由于 DOM3 級(jí)不提倡使用 keypress 事件秧秉,因此只能利用這種技術(shù)來(lái)模擬 keydown 和 keyup 事件。

var textbox = document.getElementById("myTextbox"),
    event;

// 以 DOM3 級(jí)方式創(chuàng)建事件對(duì)象
if (document.implementation.hasFeature("KeyboardEvents", "3.0")){
    event = document.createEvent("KeyboardEvent");

    // 初始化事件對(duì)象
    event.initKeyboardEvent("keydown", true, true, document.defaultView, "a", 0, "Shift", 0);
}

// 觸發(fā)事件
textbox.dispatchEvent(event); 

??上述例子模擬的是按住 Shift 的同時(shí)又按下 A 鍵衰抑。在使用 document.createEvent("KeyboardEvent")之前象迎,應(yīng)該先檢測(cè)瀏覽器是否支持 DOM3 級(jí)事件;其他瀏覽器返回一個(gè)非標(biāo)準(zhǔn)的 KeyboardEvent 對(duì)象呛踊。

??在 Firefox 中挖帘,調(diào)用 createEvent() 并傳入"KeyEvents"就可以創(chuàng)建一個(gè)鍵盤事件。返回的事件對(duì)象會(huì)包含一個(gè) initKeyEvent()方法恋技,這個(gè)方法接受下列 10 個(gè)參數(shù)拇舀。

  • type(字符串):表示要觸發(fā)的事件類型,如"keydown"蜻底。
  • bubbles(布爾值):表示事件是否應(yīng)該冒泡骄崩。為精確模擬鼠標(biāo)事件聘鳞,應(yīng)該設(shè)置為 true。
  • cancelable(布爾值):表示事件是否可以取消要拂。為精確模擬鼠標(biāo)事件抠璃,應(yīng)該設(shè)置為 true。
  • view(AbstractView):與事件關(guān)聯(lián)的視圖脱惰。這個(gè)參數(shù)幾乎總是要設(shè)置為 document.defaultView搏嗡。
  • ctrlKey(布爾值):表示是否按下了 Ctrl 鍵。默認(rèn)值為 false拉一。
  • altKey(布爾值):表示是否按下了 Alt 鍵采盒。默認(rèn)值為 false。
  • shiftKey(布爾值):表示是否按下了 Shift 鍵蔚润。默認(rèn)值為 false磅氨。
  • metaKey(布爾值):表示是否按下了 Meta 鍵。默認(rèn)值為 false嫡纠。
  • keyCode(整數(shù)):被按下或釋放的鍵的鍵碼烦租。這個(gè)參數(shù)對(duì) keydown 和 keyup 事件有用,默認(rèn)值為 0除盏。
  • charCode(整數(shù)):通過按鍵生成的字符的 ASCII 編碼叉橱。這個(gè)參數(shù)對(duì) keypress 事件有用,默認(rèn)值為 0者蠕。

??將創(chuàng)建的 event 對(duì)象傳入到 dispatchEvent() 方法就可以觸發(fā)鍵盤事件窃祝,如下面的例子所示。

// 只適用于 Firefox
var textbox = document.getElementById("myTextbox");

// 創(chuàng)建事件對(duì)象
var event = document.createEvent("KeyEvents");

// 初始化事件對(duì)象
event.initKeyEvent("keypress", true, true, document.defaultView, false, false, false, false, 65, 65);

// 觸發(fā)事件
textbox.dispatchEvent(event);

??在 Firefox 中運(yùn)行上面的代碼蠢棱,會(huì)在指定的文本框中輸入字母 A锌杀。同樣,也可以依此模擬 keyup 和 keydown 事件泻仙。
??在其他瀏覽器中糕再,則需要?jiǎng)?chuàng)建一個(gè)通用的事件,然后再向事件對(duì)象中添加鍵盤事件特有的信息玉转。例如:

var textbox = document.getElementById("myTextbox");

// 創(chuàng)建事件對(duì)象
var event = document.createEvent("Events");

// 初始化事件對(duì)象
event.initEvent(type, bubbles, cancelable);
event.view = document.defaultView;
event.altKey = false;
event.ctrlKey = false;
event.shiftKey = false;
event.metaKey = false;
event.keyCode = 65;
event.charCode = 65;

// 觸發(fā)事件
textbox.dispatchEvent(event);

??以上代碼首先創(chuàng)建了一個(gè)通用事件突想,然后調(diào)用 initEvent() 對(duì)其進(jìn)行初始化,最后又為其添加了鍵盤事件的具體信息究抓。
??在此必須要使用通用事件猾担,而不能使用 UI 事件,因?yàn)?UI 事件不允許向 event 對(duì)象中再添加新屬性(Safari 除外)刺下。像這樣模擬事件雖然會(huì)觸發(fā)鍵盤事件绑嘹,但卻不會(huì)向文本框中寫入文本,這是由于無(wú)法精確模擬鍵盤事件所造成的橘茉。

3. 模擬其他事件

??雖然鼠標(biāo)事件和鍵盤事件是在瀏覽器中最經(jīng)常模擬的事件工腋,但有時(shí)候同樣需要模擬變動(dòng)事件和 HTML 事件姨丈。要模擬變動(dòng)事件,可以使用 createEvent("MutationEvents") 創(chuàng)建一個(gè)包含 initMutationEvent() 方法的變動(dòng)事件對(duì)象擅腰。這個(gè)方法接受的參數(shù)包括:type蟋恬、bubbles、cancelable趁冈、relatedNode歼争、preValue、newValue渗勘、attrName 和 attrChange沐绒。下面來(lái)看一個(gè)模擬變動(dòng)事件的例子。

var event = document.createEvent("MutationEvents");
event.initMutationEvent("DOMNodeInserted", true, false, someNode, "","","",0);
target.dispatchEvent(event);

??以上代碼模擬了 DOMNodeInserted 事件呀邢。其他變動(dòng)事件也都可以照這個(gè)樣子來(lái)模擬洒沦,只要改一改參數(shù)就可以了豹绪。

??要模擬 HTML 事件价淌,同樣需要先創(chuàng)建一個(gè) event 對(duì)象——通過 createEvent("HTMLEvents"),然后再使用這個(gè)對(duì)象的 initEvent() 方法來(lái)初始化它即可瞒津,如下面的例子所示蝉衣。

var event = document.createEvent("HTMLEvents");
event.initEvent("focus", true, false);
target.dispatchEvent(event);

??這個(gè)例子展示了如何在給定目標(biāo)上模擬 focus 事件。模擬其他 HTML 事件的方法也是這樣巷蚪。
??瀏覽器中很少使用變動(dòng)事件和 HTML 事件病毡,因?yàn)槭褂盟鼈儠?huì)受到一些限制。

4. 自定義 DOM 事件

??DOM3 級(jí)還定義了“自定義事件”屁柏。自定義事件不是由 DOM 原生觸發(fā)的啦膜,它的目的是讓開發(fā)人員創(chuàng)建自己的事件。
??要?jiǎng)?chuàng)建新的自定義事件淌喻,可以調(diào)用 createEvent("CustomEvent")僧家。返回的對(duì)象有一個(gè)名為 initCustomEvent() 的方法,接收如下 4 個(gè)參數(shù)裸删。

  • type(字符串):觸發(fā)的事件類型八拱,例如"keydown"。
  • bubbles(布爾值):表示事件是否應(yīng)該冒泡涯塔。
  • cancelable(布爾值):表示事件是否可以取消肌稻。
  • detail(對(duì)象):任意值,保存在 event 對(duì)象的 detail 屬性中匕荸。

??可以像分派其他事件一樣在 DOM 中分派創(chuàng)建的自定義事件對(duì)象爹谭。例如:

var div = document.getElementById("myDiv"),
    event;

EventUtil.addHandler(div, "myevent", function(event){
    alert("DIV: " + event.detail);
});
EventUtil.addHandler(document, "myevent", function(event){
    alert("DOCUMENT: " + event.detail);
});

if (document.implementation.hasFeature("CustomEvents", "3.0")){
    event = document.createEvent("CustomEvent");
    event.initCustomEvent("myevent", true, false, "Hello world!");
    div.dispatchEvent(event);
}

??上述例子創(chuàng)建了一個(gè)冒泡事件"myevent"。而 event.detail 的值被設(shè)置成了一個(gè)簡(jiǎn)單的字符串榛搔,然后在<div>元素和 document 上偵聽這個(gè)事件诺凡。
??因?yàn)?initCustomEvent() 方法已經(jīng)指定這個(gè)事件應(yīng)該冒泡齿风,所以瀏覽器會(huì)負(fù)責(zé)將事件向上冒泡到 document。
??支持自定義 DOM 事件的瀏覽器有 IE9+和 Firefox 6+绑洛。

6.2救斑、 IE 中的事件模擬

??在 IE8 及之前版本中模擬事件與在 DOM 中模擬事件的思路相似:先創(chuàng)建 event 對(duì)象,然后為其指定相應(yīng)的信息真屯,然后再使用該對(duì)象來(lái)觸發(fā)事件脸候。當(dāng)然,IE 在實(shí)現(xiàn)每個(gè)步驟時(shí)都采用了不一樣的方式绑蔫。
??調(diào)用 document.createEventObject() 方法可以在 IE 中創(chuàng)建 event 對(duì)象运沦。但與 DOM 方式不同的是,這個(gè)方法不接受參數(shù)配深,結(jié)果會(huì)返回一個(gè)通用的 event 對(duì)象携添。
??然后,你必須手工為這個(gè)對(duì)象添加所有必要的信息(沒有方法來(lái)輔助完成這一步驟)篓叶。
??最后一步就是在目標(biāo)上調(diào)用 fireEvent() 方法烈掠,這個(gè)方法接受兩個(gè)參數(shù):事件處理程序的名稱和 event 對(duì)象。
??在調(diào)用 fireEvent()方法時(shí)缸托,會(huì)自動(dòng)為 event 對(duì)象添加 srcElement 和 type 屬性左敌;其他屬性則都是必須通過手工添加的。換句話說俐镐,模擬任何 IE 支持的事件都采用相同的模式矫限。例如,下面的代碼模擬了在一個(gè)按鈕上觸發(fā) click 事件過程佩抹。

var btn = document.getElementById("myBtn");

// 創(chuàng)建事件對(duì)象
var event = document.createEventObject();

// 初始化事件對(duì)象
event.screenX = 100;
event.screenY = 0;
event.clientX = 0;
event.clientY = 0;
event.ctrlKey = false;
event.altKey = false; 
event.shiftKey = false;
event.button = 0;

// 觸發(fā)事件
btn.fireEvent("onclick", event);

??上述例子先創(chuàng)建了一個(gè) event 對(duì)象叼风,然后又用一些信息對(duì)其進(jìn)行了初始化。注意棍苹,這里可以為對(duì)象隨意添加屬性无宿,不會(huì)有任何限制——即使添加的屬性 IE8 及更早版本并不支持也無(wú)所謂。在此添加的屬性對(duì)事件沒有什么影響,因?yàn)橹挥惺录幚沓绦虿艜?huì)用到它們。

??采用相同的模式也可以模擬觸發(fā) keypress 事件钞它,如下面的例子所示。

var textbox = document.getElementById("myTextbox");

// 創(chuàng)建事件對(duì)象
var event = document.createEventObject();

// 初始化事件對(duì)象
event.altKey = false;
event.ctrlKey = false;
event.shiftKey = false;
event.keyCode = 65;

// 觸發(fā)事件
textbox.fireEvent("onkeypress", event);

??由于鼠標(biāo)事件梭灿、鍵盤事件以及其他事件的 event 對(duì)象并沒有什么不同,所以可以使用通用對(duì)象來(lái)觸發(fā)任何類型的事件冰悠。
??不過堡妒,正如在DOM中模擬鍵盤事件一樣,運(yùn)行這個(gè)例子也不會(huì)因模擬了keypress 而在文本框中看到任何字符溉卓,即使觸發(fā)了事件處理程序也沒有用皮迟。

小結(jié)

??事件是將 JavaScript 與網(wǎng)頁(yè)聯(lián)系在一起的主要式搬泥。“DOM3 級(jí)事件”規(guī)范和 HTML5 定義了常見的大多數(shù)事件伏尼。即使有規(guī)范定義了基本事件忿檩,但很多瀏覽器仍然在規(guī)范之外實(shí)現(xiàn)了自己的專有事件,從而為開發(fā)人員提供更多掌握用戶交互的手段爆阶。
??有些專有事件與特定設(shè)備關(guān)聯(lián)燥透,例如移動(dòng) Safari 中的 orientationchange 事件就是特定關(guān)聯(lián) iOS 設(shè)備的。

??在使用事件時(shí)辨图,需要考慮如下一些內(nèi)存與性能方面的問題班套。

  • 有必要限制一個(gè)頁(yè)面中事件處理程序的數(shù)量,數(shù)量太多會(huì)導(dǎo)致占用大量?jī)?nèi)存故河,而且也會(huì)讓用戶感覺頁(yè)面反應(yīng)不夠靈敏吱韭。
  • 建立在事件冒泡機(jī)制之上的事件委托技術(shù),可以有效地減少事件處理程序的數(shù)量鱼的。
  • 建議在瀏覽器卸載頁(yè)面之前移除頁(yè)面中的所有事件處理程序理盆。

??可以使用 JavaScript 在瀏覽器中模擬事件≡“DOM2 級(jí)事件”和“DOM3 級(jí)事件”規(guī)范規(guī)定了模擬事件的方法熏挎,為模擬各種有定義的事件提供了方便速勇。此外晌砾,通過組合使用一些技術(shù),還可以在某種程度上模擬鍵盤事件烦磁。IE8 及之前版本同樣支持事件模擬养匈,只不過模擬的過程有些差異。

??事件是 JavaScript 中最重要的主題之一都伪,深入理解事件的工作機(jī)制以及它們對(duì)性能的影響至關(guān)重要呕乎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市陨晶,隨后出現(xiàn)的幾起案子猬仁,更是在濱河造成了極大的恐慌,老刑警劉巖先誉,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湿刽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡褐耳,警方通過查閱死者的電腦和手機(jī)诈闺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铃芦,“玉大人雅镊,你說我怎么就攤上這事襟雷。” “怎么了仁烹?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵耸弄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我卓缰,道長(zhǎng)叙赚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任僚饭,我火速辦了婚禮震叮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鳍鸵。我一直安慰自己苇瓣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布偿乖。 她就那樣靜靜地躺著击罪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贪薪。 梳的紋絲不亂的頭發(fā)上媳禁,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音画切,去河邊找鬼竣稽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛霍弹,可吹牛的內(nèi)容都是我干的毫别。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼典格,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼岛宦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起耍缴,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤砾肺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后防嗡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體变汪,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年本鸣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疫衩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闷煤,靈堂內(nèi)的尸體忽然破棺而出童芹,到底是詐尸還是另有隱情,我是刑警寧澤鲤拿,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布假褪,位于F島的核電站,受9級(jí)特大地震影響近顷,放射性物質(zhì)發(fā)生泄漏生音。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一窒升、第九天 我趴在偏房一處隱蔽的房頂上張望缀遍。 院中可真熱鬧,春花似錦饱须、人聲如沸域醇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)譬挚。三九已至,卻和暖如春酪呻,著一層夾襖步出監(jiān)牢的瞬間减宣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工玩荠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漆腌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓姨蟋,卻偏偏與公主長(zhǎng)得像屉凯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眼溶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容