在這一章我會講解給元素注冊事件的最好的一種辦法,那就是:確保一個特定的事件在特定的HTML元素上發(fā)生并且能運行特定的腳本。
在最古老的JavaScript瀏覽器里注冊事件只能通過內(nèi)聯(lián)模式。自從DHTML從根本上改變了你操作頁面的方法,事件的注冊就必須有擴(kuò)展性而且要
有很強(qiáng)的適應(yīng)性。所以就必須有相應(yīng)的事件模型。Netscape在第三代瀏覽器中就開始了,IE在第四代瀏覽器開始。
因為Netscape 3就開始支持這種新的事件注冊模型,在瀏覽器戰(zhàn)爭前就是事實上的標(biāo)準(zhǔn)。所以微軟不得不也是最后一次為了網(wǎng)上那些數(shù)不清的
使用了Netscape事件處理模型的頁面在兼容性上做出了讓步。
所以這兩個瀏覽器,事實上也是所有的瀏覽器都支持下面的代碼:
1 element.onclick = doSomething;
這是注冊一個事件的最好的辦法。無論什么時候用戶點擊了這個HTML元素,那么doSomething()都會執(zhí)行。這是唯一一個能夠跨瀏覽的注冊事件
的最好的辦法,深刻的理解這個模型和他的限制也是非常重要的。
因為沒有官方的標(biāo)準(zhǔn),所以我暫且稱為傳統(tǒng)事件注冊模型(traditional event registration model)。同時,w3c也標(biāo)準(zhǔn)化了事件注冊,微軟也
推出了高級模式,但是傳統(tǒng)模式依然能很好的運行。
高級事件注冊程序
從Netscape 3/IE 4開始,JavaScript能夠識別元素上的一系列事件的屬性。大多數(shù)HTML元素都有onclick,onmouseover,onkeypress等等屬性。
那些元素有哪些屬性--哪些元素支持哪些事件--都依賴于瀏覽器。
這些屬性對于他們本身也不是什么新穎的東西。在最古老的JavaScript瀏覽器里面就已經(jīng)存在了。
1 <a href="somewhere.html" onclick="doSomething()">
這里的A標(biāo)簽就有一個onclick參數(shù),在JavaScript里面就成為了A元素的屬性。那些古老的瀏覽器的事件處理程序只能通過在頁面源代碼里面設(shè)
置元素的參數(shù)這個辦法來注冊。如果你想讓這個腳本在所有的A標(biāo)簽執(zhí)行,那么你就需要再所有的鏈接上面加上onclick事件。
有了傳統(tǒng)事件注冊模型的到來,這些onclick,onmouseover或者HTML元素的其他事件處理就都可以通過JavaScript來注冊了,F(xiàn)在你可以添加
、修改或者刪除一些事件處理程序而不用動HTML的一絲一毫。當(dāng)你通過DOM來訪問HTML元素的時候你就可以像下面這樣寫代碼了:
1 element.onclick = doSomething;
現(xiàn)在我們的示例函數(shù)doSomething()就注冊在了element元素的onclick屬性上,而且當(dāng)用戶點擊了這個元素函數(shù)就會執(zhí)行。注意事件的名字必須
都是小寫。
刪除這個事件處理程序,只要簡單的讓點擊事件為空就行了:
1 element.onclick = null;
事件處理程序跟普通的JavaScript函數(shù)一樣。即使事件沒有發(fā)生的時候他也能執(zhí)行。如果你則這樣寫:
1 element.onclick()
那么doSomething一樣會執(zhí)行。雖然如果是一個不知道做什么或者產(chǎn)生錯誤的函數(shù),這也沒有真實的事件發(fā)生。所以這是一種很少用來執(zhí)行事件
處理程序的方法。
微軟的IE5.5和更高版本的IE還有一個fireEvent()方法來完成同樣的事情。使用如下:
1 element.fireEvent('onclick')
沒有括號
需要注意的是注冊一個事件處理程序的時候你不能使用括號。onclick方法會被設(shè)置成為另外一個函數(shù)。如果你這樣寫
1 element.onclick = doSomething();
那么這個函數(shù)就會執(zhí)行并且它的結(jié)果會被注冊到onclick上。這可不是我們所期望的,我們只是希望在事件發(fā)生的時候函數(shù)能夠執(zhí)行。另外函數(shù)
寫出來是為了在事件發(fā)生的時候執(zhí)行,如果沒有關(guān)聯(lián)的執(zhí)行會造成嚴(yán)重的混亂和錯誤。
所以我們在事件處理程序中復(fù)制整個doSomething()方法。我們只是想在事件執(zhí)行的時候執(zhí)行這個函數(shù)。
this
在JavaScript里this關(guān)鍵字通常指函數(shù)的所有者。如果this指向事件發(fā)生的HTML元素,那么一切都是那么的美好,你可以很簡單的做很多事情
。
不幸的是,雖然this非常的強(qiáng)大,但是如果你不是明確的知道他怎么運作的話使用起來還是比較難的。關(guān)于這個我在另一個地方有詳細(xì)的討論
,在這我在傳統(tǒng)模式下做一些概述。
在傳統(tǒng)模式里this工作如下;注意這個跟內(nèi)聯(lián)模式稍微有些不同,F(xiàn)在this關(guān)鍵字在函數(shù)里,而不是在HTML的參數(shù)上。這個區(qū)別后面會另外講
的。
1 element.onclick = doSomething;
2 another_element.onclick = doSomething;
3
4 function doSomething() {
5 this.style.backgroundColor = '#cc0000';
6 }
如果你注冊了doSomething()作為任何一個HTML元素的click事件,那么當(dāng)用戶點擊那個元素的時候元素就得到一個背景。
匿名函數(shù)(Anonymous functions)
假設(shè)你想所有div在鼠標(biāo)經(jīng)過的時候改變背景色,然后在鼠標(biāo)離開的時候返回背景色。正確的使用this,你可以這樣寫:
01 var x = document.getElementsByTagName('DIV');
02 for (var i=0;i<x.length;i++) {
03 x[i].onmouseover = over;
04 x[i].onmouseout = out;
05 }
06
07 function over() {
08 this.style.backgroundColor='#cc0000'
09 }
10
11 function out() {
12 this.style.backgroundColor='#ffffff'
13 }
這些代碼可以運行,沒問題。但是既然over()和out()都比較簡單,那么就可以用一種更優(yōu)雅的匿名函數(shù)的方法來寫:
1 ...
2 for (var i=0;i<x.length;i++) {
3 x[i].onmouseover = function () {this.style.backgroundColor='#cc0000'}
4 x[i].onmouseout = function () {this.style.backgroundColor='#ffffff'}
5 }
反正onmouseover和onmouseout都是得到一個函數(shù)。與其拷貝over()和out(),不如直接定義一個事件處理程序在這個事件注冊的腳本上。既然
這些函數(shù)沒有名字,那么他們就是匿名函數(shù)。
這兩種注冊事件處理程序的方法基本上一樣,唯一的區(qū)別就是第二種的代碼量少一些。我非常喜歡匿名函數(shù)并且我會在注冊一個簡單的事件處
理程序的時候使用它。
問題
有一個小小的問題就是傳統(tǒng)模式下onclick屬性只能包含一個函數(shù)。當(dāng)你想對一個事件注冊多個事件處理程序的時候就有問題了。
比如,你已經(jīng)寫了一個可以拖動的模塊。這個模塊注冊在onclick事件處理程序上所以當(dāng)你點擊它的時候就能開始拖動。你還寫了一個模塊可以
跟蹤用戶的點擊然后在onunload的時候發(fā)送信息給服務(wù)器,這樣就能知道你的頁面如何被使用的。這個模塊也需要在元素上注冊一個onclick事
件。
所以事情可能會是這樣:
1 element.onclick = startDragDrop;
2 element.onclick = spyOnUser;
這是就會發(fā)生錯誤。第二個注冊程序會覆蓋第一個,那么當(dāng)用戶點擊元素的時候就只有spyOnUser()執(zhí)行。
解決辦法就是注冊一個包含兩個方法的方法:
1 element.onclick = function () {startDragDrop(); spyOnUser()}
靈活的注冊
但是假設(shè)你沒有在你網(wǎng)站的每個頁面都使用兩個模塊。如果你還這樣寫:
1 element.onclick = function () {startDragDrop(); spyOnUser()}
你會得到一個錯誤信息因為其中有個函數(shù)是未定義的。所以在注冊事件的時候要特別的小心。當(dāng)我們在startDragDrop()可能已經(jīng)注冊的時候還
想注冊spyOnUser(),那么我們可以這樣寫:
1 var old = (element.onclick) ? element.onclick : function () {};
2 element.onclick = function () {old(); spyOnUser()};
首先你定義一個變量old。如果元素已經(jīng)有了一個onclick的事件處理程序,那么就把它存入old,如果沒有,就設(shè)置old為一個空的function。
現(xiàn)在你要給一個div注冊一個新的事件處理程序。那么程序就會首先執(zhí)行old(),然后執(zhí)行spyOnUser()。現(xiàn)在新的事件處理程序添加在了元素上
,之前的注冊過的(如果有)也被包含了。
最后一個問題:如果你想移除其中一個事件處理程序呢?現(xiàn)在我還不是很確定。你需要通過一些方法編輯element.onclick(),我還沒有研究過
這個問題。
其他模式
我們看到傳統(tǒng)模式非常的簡單易用,但是當(dāng)你給一個事件添加幾個程序的時候的解決辦法還是比較丑陋的。W3C的事件處理程序很好的解決了這
個問題。
繼續(xù)
如果你想繼續(xù)學(xué)習(xí),請看下一章。