Javascript事件簡介+取消默認動作

JavaScript事件簡介

  審視任何JavaScript代碼的核心,你會發現正是事件是把所有東西融合在一些。在一個設計良好的JavaScript應用程序裏,你將擁有數據源和它的視覺的表示(在HTML DOM內部)。爲了同步這兩個方面,你必須監視用戶的交互動作並試圖相應地更新用戶界面。DOM和JavaScript事件的結合是任何現代web應用程序賴以工作的至關重要的組合。

異步事件vs.線程

  JavaScript裏的事件系統是相當獨特的。它完全地異步工作而根本不使用線程。這意味着你程序中的所有代碼將依賴於其它的動作——比如用戶的點擊或者頁面的加載——來觸發。
  線程化的程序設計與異步的程序設計根本的不同點在於你怎樣等待事情發生。在線程化的程序裏,你需要不停地反覆檢查條件是否滿足了。而在異步程序裏你只須簡單地通過事件句柄註冊一個回調函數,一旦事件發生,句柄就會通過執行回調函數來讓你知道。我們來探索一下假如使用線程JavaScript程序將會怎麼編寫和實際使用異步回調函數JavaScript又是怎麼編寫的。

  JavaScript線程

  按目前的情況來看,JavaScript線程並不存在。你最多是使用setTimeout回調函數來模擬,但即使是那樣,也並不理想。程序6-1中所示是一段假想的線程化的JavaScript代碼,在其中你正在等待,直到頁面完成加載。如果JavaScript真是一個線程化語言的語言,你將不得不做那樣的事。

  程序6-3. 模擬線程的JavaScript僞碼

複製內容到剪貼板
代碼:
// 注意:這段代碼不能生效!
// 在頁面加載之前,持續地檢查
while ( ! window.loaded() ) { }
//頁面現在已經加載了,於是開始做些事情
document.getElementByIdx_x("body").style.border = "1px solid #000";

  如你所見,這段代碼裏有一個循環,一直在檢查window.loaded()是否返回true。且不說window對象根本沒有loaded()這個函數,那樣的循環也決不會在JavaScript中起作用。這是因爲JavaScript中的循環是阻塞式的(也就是說它們運行完成之前別的什麼事都不會發生)。假如JavaScript能夠處理線程,你看到的情形將如圖6-1所示。在圖中,代碼中的while循環持續地檢查window是否已經加載。



  在實際的情況裏,因爲while循環持續地運行並阻斷了應用程序的正常流程,true值永遠不可到達。結果是用戶的瀏覽器將會停止響應並可能崩潰。由此可知,如果有任何人聲稱在JavaScript裏用while循環等待動作能夠成功,他要麼是說着玩,要麼是迷糊得厲害。

  異步回調函數

  使用線程不斷檢查更新的替代方案是使用異步的回調,這正是JavaScript所使用的。直白地說,你告訴一個DOM元素,當指定的事件發生,你想要一個函數被調用以處理它。這意味着你只提供一個對希望執行的代碼的引用,而瀏覽器處理所有的細節。程序6-2展示了使用事件句柄和回調函數的一段簡單的代碼。你會看到在JavaScript裏把一個函數綁定到事件句柄(window.onload)上所需要的實際的代碼。一但頁面加載完成,window.onload就會被調用。其它通常的事件如click,mousemove和submit的情形也是這樣。

  程序6-2. JavaScript裏的異步回調

複製內容到剪貼板
代碼:
//註冊一個頁面加載完成時被調用的函數
window.onload = loaded;
//頁面加載完成時被調用的函數
function loaded() {
    //頁面現己加載完成了,於是乾點事情
    document.getElementByIdx_x("body").style.border = "1px solid #000";
}

  將程序6-2的代碼與6-1中的進行比較,你會看到顯著的不同。唯一被立即執行的代碼是將事件句柄(loaded函數)向事件監聽器(onload屬性)的綁定。一旦頁面完全加載,瀏覽器將調用與window.onload相關聯的函數並執行它。因爲實際上不可能等待事情的發生,你將一個回調函數(loaded)註冊到頁面加載完成時會被調用的句柄(window.onload)上。



  我們的簡單的事件監聽器和處理程序還沒有立即顯現的一個問題是,取決於事件類型和元素在DOM中位置的不同,事件會變得多樣化並能以不同的方式來處理。下一節我們將看到事件的兩個階段及其不同點。

事件的階段

  JavaScript事件分爲兩個階段執行:捕獲(capturing)和冒泡(bubbling)。這意味着當事件從一個元素觸發時(比如,用戶點擊一個鏈接導致click事件被觸發),哪些元素允許處理它、以什麼順序處理它,變得多樣化了。

  從這個簡單的點擊鏈接的例子裏,你們可以看到事件的執行順序。假設用戶點擊了一個<a>元素,文檔的click句柄首先被觸發,然後是<body>的句柄,然後是<div>的,等等,一直下行到<a>元素;這稱爲捕獲階段。此階段完成以後,它又再次沿着樹往上爬,<li>,<ul>,<div>,<body>,以及文檔的事件句柄依次全部被觸發。
  爲什麼事件處理會以這種方式建立有着很特別的原因,它工作得也非常好。假設你想每一個<li>元素在用戶把鼠標移到上面時會改變背景顏色,當鼠標移開時又變回來(這是許多菜單的一般需要),程序6-3裏的代碼可以確切地做到這一點。

  程序6-3. 帶鼠標懸停效果的標籤導航方案

複製內容到剪貼板
代碼:
//查找所有的<li>元素,並附以事件處理函數
var li = document.getElementsByTagName_r("li");
for ( var i = 0; i < li.length; i++ ) {
    //爲<li>元素附加mouseover事件處理函數,
    //用來將元素的背景色改爲藍色
    li[i].onmouseover = function() {
        this.style.backgroundColor = 'blue';
    };
    //爲<li>元素附加mouseout事件處理函數,
    //用來將元素的背景色改回缺省的白色
    li[i].onmouseout = function() {
        this.style.backgroundColor = 'white';
    };
}

  這些代碼會確實如你所設想的那樣運作:鼠標移到<li>元素上,它的背景色會改變,把鼠標移開,顏色又將還願。但是,你可能沒有意識到的是,當你每次把鼠標移到<li>的時候你實際上切換了兩個元素。因爲<li>元素包含<a>元素,你的鼠標同樣滑過了它,而不僅僅是<li>元素。我們來看看事件調用的精確的流程:
  1. <li> mouseover: 鼠標到了<li>元素上
  2. <li> mouseout: 鼠標從<li>移到了它所包含的<a>元素
  3. <a> mouseover: 鼠標現在到了<a>元素上
  4. <li> mouseover: <a>的mousever事件向上冒泡成爲<li>的mouseover
  從事件調用的方式上你可能已經覺察到,你完全忽略了事件的捕獲階段;不用擔心,我可沒忘記它。你綁定事件監聽器的方式是古老的"傳統"方式:設置元素的onevent屬性;它只支持事件冒泡,不支持捕獲。事件的這一綁定方式及其它方式,將在下一主題論述。
  除了事件調用的奇怪的順序以外,你可能還注意到了兩個意外的動作:鼠標移出<li>元素和<a>向<li>的mouseover冒泡。我們來仔細地看看。
  第一個mouseover事件發生,因爲如瀏覽器認爲,你離開了父級<li>元素的範圍,進入了另一個元素。這是因爲當前位於最上層的元素(正如<a>相對於其父元素<li>)將會接收到鼠標即時的焦點。
  <a>的mouseover向<li>元素的冒泡最終成就了我們那一段代碼的優美。因爲你實際上沒有綁定任何種類的監聽器給<a>元素,事件於是簡單地沿着DOM上行,尋找另一個正在監聽的元素。冒泡過程中它所遇到的第一個元素是<li>元素,恰巧監聽着鼠標移入事件(這也正好是你想要的)。
  你需要考慮的是,要是你確實給<a>元素的mouseover綁定了事件處理程序呢?有什麼方法可以停止事件的冒泡嗎?這是我將要論述的另一個重要的主題。
 

事件的一般特性

  JavaScript事件很好一面是,它們有着一些相對一致的特性,給予你開發時的更多的能力和控制。最簡單和最古老的概念是事件對象,它給你一系列的元數據和上下文相關的函數,允許你處理諸如鼠標事件和鍵盤按鍵事件等。另外,有一些函數用來修改事件的通常的捕獲/冒泡流程。深入學習這些特性可以讓你事半功倍。

事件對象

  事件處理函數的一個標準功能是以某種方式訪問包含當前事件的上下文信息的事件對象。這一對象在特定的事件中充當着非常有用的資源。比如,當處理鍵盤按下的事件時,你可以訪問事件對象的keyCode屬性,以得知被按下的是哪的鍵。在附錄B中可以找到關於事件對象的更詳細的說明。
  然而,事件對象棘手之處在於,IE的實現與W3C的規範並不相同。IE有一個單獨的全局事件對象(可以可靠地通過全局屬性window.event訪問),而其它的每一種瀏覽器都把事件對象作爲單個的參數傳遞給事件處理函數。可靠地使用事件對象的一個例子見程序6-4,代碼修改一個普通的<textarea>元素,使其行爲發生了改變。典型地,用戶可以在<textarea>裏按下回車鍵,產生一個換行符。但是假如你不希望那樣做呢?函數正是提供了這一功能。

  程序6-4. 使用DOM事件重寫功能

複製內容到剪貼板
代碼:
//找到頁面裏的第一個<textarea>併爲它綁定kerpress監聽器
document.getElementsByTagName_r("textarea")[0].onkeypress = function(e){
    //如果不存在事件對象,就抓取那個全局的(ie only)
    e = e || window.event;
    //如果按下了回車鍵,返回false(導致它什麼也不幹)
    return e.keyCode != 13;
};

  事件對象包含了大量的屬性和函數,且它們的命名與行爲在瀏覽器之間各不相同。我不想現在就進入那些細節,但是我強烈建議你閱讀附錄B,那是所有的事件對象屬性的一個的列表,包括使用方法以及實際使用中的例子。

this關鍵字

  this關鍵字(見第二章)提供了一種在函數作用域中的訪問當前對象的方式。現代瀏覽器使用this關鍵字給所有的事件處理函數提供上下文信息。它們中只有一部分(而且只有部分方法)良好地運行,將它設爲當前對象;這將很快地被深入討論到。例如,在程序6-5中,我可以利用這一事實,只建立一個通用的函數來處理所有點擊而通過this關鍵來確定作用於哪一個元素,它將如預期地工作。

  程序6-5. 點擊時改變<li>元素的背景色

複製內容到剪貼板
代碼:
//查看所有的<li>元素並給每一個綁定click處理函數
var li = document.getElementsByTagName_r("li");
for ( var i = 0; i < li.length; i++ ) {
    li[i].onclick = handleClick;
}
//click處理函數,調用時改變特定元素的前景色和背景色
function handleClick() {
    this.style.backgroundColor = "blue";
    this.style.color = "white";
}

  this關鍵字的確只是爲了方便而設的,但我想你會發現,當使用它的屬性,將會極大地降低你的JavaScript代碼的複雜性。在本書中,我將試圖使用this關鍵字編寫所有的事件相關的代碼。

取消事件冒泡

  知道了事件的捕獲/冒泡怎樣工作以後,我們再來探討怎樣控制它。前面的例子裏引入的一個很重要的問題是,如果你想要一個事件只在其目標上而不在基父級元素上出現,你處配辦法停止它。



  停止事件的冒泡(或捕獲)被證明在複雜的應用程序中是極其有用的。不幸的是,IE提供了一種與所有其它瀏覽器不同的方式來阻止事件冒泡。程序6-6是一個通用的取消事件冒泡的函數。該函數接受單個參數:傳遞到事件處理程序的事件對象。該函數處理取消事件冒泡的兩種方式:標準的W3C方式和非標準的IE方式。

  程序6-6. 停止事件冒泡的通用函數

複製內容到剪貼板
代碼:
function stopBubble(e) {
    //如果提供了事件對象,則這是一個非IE瀏覽器
    if ( e && e.stopPropagation )
        //因此它支持W3C的stopPropagation()方法
        e.stopPropagation();
    else
        //否則,我們需要使用IE的方式來取消事件冒泡
        window.event.cancelBubble = true;
}

  現在你可能想知道的是,什麼時候我想要阻止事件冒泡?老實說,多數時間裏你可能從來不需擔心這個。當你開始開發動態的應用程序(尤其是需要處理鍵盤和鼠標事件)時,這一需求才會變得突出。
  程序6-7展示了一個簡明的代碼片段:爲你鼠標懸停的當前元素加上紅色邊框。如果不阻止事件冒泡,每一次你把鼠標移動到一個元素時,該元素及其所有的父級元素都將有一個並非我們想要的紅色的邊框。

  程序6-7. 使用stopBubble()創建一系列交互式的元素

複製內容到剪貼板
代碼:
//查找並遍歷DOM中的所有元素
var all = document.getElementsByTagName_r("*");
for ( var i = 0; i < all.length; i++ ) {
    //監視用戶何時把鼠標移到元素上,
    //爲該元素添加紅色邊框
    all[i].onmouseover = function(e) {
        this.style.border = "1px solid red";
        stopBubble( e );
    };
    //監視用戶何時把鼠標移出元素,
    //刪除我們所添加的紅色邊框
    all[i].onmouseout = function(e) {
        this.style.border = "0px";
        stopBubble( e );
    };
}

  擁有阻止事件冒泡的能力,你就能對事件到達哪個元素並進行處理有了完全的控制。這是開發動態的web應用程序所需的一個非常工具。最後一點,取消瀏覽器的默認動作,這允許你完全改寫瀏覽器的行爲並實現新的功能以替代之。

改寫瀏覽器的默認動作

  對於發生的大多數事件,瀏覽器有一些總會發生的默認動作。比如說,點擊一個<a>元素將會把你帶到它所關聯的網頁;這是瀏覽器的一個默認動作。這一動作總是在事件的捕獲和冒泡階段都完成以後發生。該示例說明了用戶在頁面點擊<a>元素的結果。事件起初經歷在 DOM中的捕獲和冒泡階段(如前所述)。然而,一旦事件完成了其旅程,瀏覽器將試圖執行該事件及元素的默認動作。在這裏也就是訪問鏈接的網頁。



  默認動作可以概括爲瀏覽器所執行的你沒有明確指定的操作。下面是特定事件發生時幾種不同類型的默認動作:
  a. 點擊一個<a>元素將會跳轉到元素的href屬性指定的URL。
  b. 按下Ctrl+S鍵,瀏覽器將試圖保存當前網頁。
  c. 提交一個HTML<form>元素將從指定URL查詢數據並將瀏覽器重定向到該地址。
  d. 移動鼠標到帶有alt或title屬性(取決於不同的瀏覽器)的<img>元素將導致出現一個工具提示,提供該<img>元素的描述。
  即使你阻止了事件的冒泡或者根本沒有設置事件處理函數,上述事件也會被瀏覽器執行。這在你的腳本中會導致顯著的問題。如果你想讓你的表單有不同的行爲呢?或者如果你想要<a>元素以不同於它們本來目的的方式運作呢?因爲取消事件冒泡不足以阻止默認行爲,你需要一些特別的代碼在直接處理它們。跟取消事件冒泡一樣,有兩種方式來阻止默認動作的發生:IE特有的方式和W3C方式。兩種方式見於程序6-8。其中展示的函數接受單個參數:傳遞到事件處理函數的事件對象,使用方法如return stopDefault(e);——當你的處理函數也需要返回false(這是stopDefault爲你所返回的)時。

  程序6-8. 阻止瀏覽器默認動作發生的通用函數

複製內容到剪貼板
代碼:
function stopDefault( e ) {
    //阻止默認瀏覽器動作(W3C)
    if ( e && e.preventDefault )
        e.preventDefault();
    //IE中阻止函數器默認動作的方式
    else
        window.event.returnValue = false;
    return false;
}

  使用stopDefault函數,你現在可以阻止瀏覽器給出的任何默認動作。這允許你用腳本爲用戶編寫出靈巧的交互,如程序6-9所示。此代碼使一個頁面內所有的鏈接在一個自包含的<iframe>中加載,而不是打開整個新的頁面。這麼做可以使你把用戶保持在頁面上,並可能給出更具交互性的體驗。
注意:在95%的情況下,阻止默認動作會生效。然而事情在你跨越瀏覽器時會變得棘手起來,因爲阻止默認事件取決於瀏覽器(它們並不總能做對),尤其是當阻止文本輸入框裏的按鍵事件的動作和阻止iframe裏的動作時;除了這些以外,應該還是足夠健全的。

  程序6-9. 使用stopDefault()來改寫瀏覽器功能
[code]
//假設頁面中已經有一個ID爲iframe的<iframe>元素
var iframe = document.getElementByIdx_x("iframe");

//查找頁面中的所有<a>元素
var a = document.getElementsByTagName_r("a");
for ( var i = 0; i < a.length; i++ ) {

       //爲<a>元素綁定事件處理函數
       a[i].onclick = function(e) {
              //設置iframe的location
              iframe.src = this.href;

              //阻止瀏覽器訪問<a>元素所指定的網頁(默認動作)
              return stopDefault( e );
       };

}
[/code]
  改寫默認事件絕對是共同組成了非侵入式腳本的DOM和事件的關鍵所在。在本章後面的"非侵入的DOM腳本"中我將立足於功能,更多地談到這一點;當你實際地將事件處理函數綁定到DOM元素時,爭論的要點出現了。事實上有三種綁定事件的方式,其中一些比另一些要好。下一節將會討論它們。
 

綁定事件監聽器

  怎樣將事件處理程序綁定到元素是JavaScript裏一直以來不斷推進的追求。起初,瀏覽器強制用戶將處理代碼內聯地寫在HTML文檔中。好在那一些技術已經變得遠遠過時了(說這是一件好事,是考慮到它與非侵入的DOM腳本里數據抽象的精神相悖)。
  當IE與NetScape激烈競爭的時候,它們各自開發出兩個獨立但又非常相似的註冊事件的模型。最終NetScape的模型被修改成爲W3C標準,而IE的則保持不變。
  於是乎,目前存在三種可用的註冊事件的方式。傳統方式是老式的內聯附加事件處理函數方式的一個分支,但是它很可靠而並能一致地工作。另外兩種是IE和 W3C的註冊事件的方式。最後,我將給出一套可靠的方法,開發者可以用它們來註冊和註銷事件而不需再擔心底層是什麼瀏覽器。

傳統綁定

  傳統的綁定事件的方式是我在本章中到目前爲止所一直使用的。它是到目前爲止最簡單最兼容的綁定事件處理程序的方式。使用這種方式時,你只需將函數作爲一個屬性附加到你想要監視的DOM元素上。6-10展示了使用傳統方式綁定事件的一些例子。

  程序6-10. 使用傳統的事件綁定方式附加事件

複製內容到剪貼板
代碼:
//找到第一個<form>元素併爲它附加“提交”事件處理函數
document.getElementsByTagName_r("form")[0].onsubmit = function(e){
    //阻止表單提交
    eturn stopDefault( e );
};
//爲文檔的body元素附加一個按鍵事件處理函數
document.body.onkeypress = myKeyPressHandler;
//爲頁面的加載事件附加一個處理函數
window.onload = function(){ … };

  這一技術有一系列的優勢和缺點,使用時必須注意。
傳統綁定的優勢:
  a. 使用傳統綁定的最大的好處在於它無比地簡單和一致,也就是說在很大程度上它能保障無論使用什麼瀏覽器都能生效。
  b. 當處理事件時,this關鍵字指向當前的元素,這一點是非常有用的(如程序6-5示範的那樣)。
傳統綁定的缺點:
  a. 傳統綁定只作用於事件冒泡,而非捕獲和冒泡。
   b. 只能每次爲一個元素綁定一個事件處理函數。當使用流行的window.onload屬性時,這將會潛在地導致令人困惑的結果(因爲它會覆蓋其它的使用相同方法綁定的代碼片段)。程序6-11展示了這一問題的一個實例,一個新的事件處理函數覆蓋了原來的事件處理函數。
  c. event對象參數只在非IE瀏覽器上有效

程序6-11. 事件處理函數互相覆蓋

複製內容到剪貼板
代碼:
//綁定初始的load處理函數
window.onload = myFirstHandler;
//在某個地方,你所引用的其它庫裏,你的第一個處理函數被覆蓋,
//頁面加載完成時只有mySecongHandler函數被調用
window.onload = mySecondHandler;

  懂得了盲目覆蓋其它事件的可能性,你可能會選擇只在可以信任所有其它的代碼簡單的情況下使用傳統綁定。解決這一混亂情況的一種方式是使用瀏覽器提供的現代綁定方法。

DOM綁定:W3C

  W3C的爲DOM元素綁定事件處理函數的方法是這方面唯一真正的標準方式。除了IE,所有其它的現代瀏覽器都支持這一事件綁定的方式。
  附加新的處理函數的代碼很簡單。它作爲每一個DOM元素的名爲addEventListener的方法存在,接收3個參數:事件的名稱(如 click),處理事件的函數,以及一個來用使用或禁用事件捕獲的布爾標誌。程序6-12展示一個實際使用addEventListener的例子。

  程序6-12. 使用W3C方式綁定事件處理函數的示例代碼片段

複製內容到剪貼板
代碼:
//找到第一個<form>元素併爲它附加“提交”事件處理函數
document.getElementsByTagName_r("form")[0].addEventListener('submit',function(e){
    //阻止表單提交
    return stopDefault( e );
}, false);
//爲文檔的body元素附加一個按鍵事件處理函數
document.body.addEventListener('keypress', myKeyPressHandler, false);
//爲頁面的加載事件附加一個處理函數
window.addEventListener('load', function(){ … }, false);

W3C綁定的優勢:
  1. 這一方法同時支持事件處理的冒泡和捕獲階段。事件的階段通過設置addEventListener的最後一個參數爲false(指示冒泡)或true(指示捕獲)來切換。
  2. 在事件處理函數內部,this關鍵字引用當前元素。
  3. 事件對象總是作爲事件處理函數的第一個參數被提供。
  4. 你可以綁定任意多個函數到一個元素上,而不會覆蓋先前所綁定的。
W3C綁定的缺點
  1. 它在IE裏面無效。你必須使用IE的attachEvent函數來代替。
  如果IE採用了W3C的方法來綁定事件處理函數,這一章將會比現在短得多,因爲那將會不再需要討論綁定事件的替代方法。然而,到目前爲止,W3C的事件綁定方法仍然是最可理解和最易使用的。

DOM綁定:IE

  在許多方面,IE的綁定事件的方式看起來跟W3C的非常相似。但是,當你觸及細節的時候,它又在某些方面有着非常顯著的不同。程序6-13是IE中綁定事件處理函數的一些例子。

程序6-13. 使用IE的方式綁定事件處理函數的示例

複製內容到剪貼板
代碼:
//找到第一個<form>元素併爲它附加“提交”事件處理函數
document.getElementsByTagName_r("form")[0].attachEvent('onsubmit',function(){
    //阻止表單提交
    return stopDefault();
});
//爲文檔的body元素附加一個按鍵事件處理函數
document.body.attachEvent('onkeypress', myKeyPressHandler);
//爲頁面的加載事件附加一個處理函數
window.attachEvent('onload', function(){ … });

IE綁定的優勢
  1. 你可以綁定任意多個函數到一個元素上,而不會覆蓋先前綁定的。
IE綁定的缺點
  1. IE只支持事件的冒泡階段。
  2. 事件監聽函數內部的this關鍵字指向window對象,而非當前函數(這是IE的巨大敗筆)。
  3. 事件對象只能從widnow.event得到。
  4. 事件名稱必須形如onxxxx——比如,要使用"onclick"而不能只是"click"。
  5. 它只對IE有效。對於非IE平臺,你必須使用W3C的addEventListener。
  相對其半標準的事件特性,IE事件綁定的實現是嚴重短缺的。鑑於它的許多不足之處,彌補方案必須繼續存在以強制它合理的運轉。然而,幸運的是,向DOM添加事件的通用的函數確實存在,它能極大的減輕我們的痛苦。

addEvent和removeEvent

  2005年末,Peter-Paul Koch( http://quirksmode.org) 發起了一個競賽,向JavaScript代碼編寫者公開徵求一對新的函數,addEvent和removeEvent,用來提供一個可靠的方式爲DOM元素添加和刪除事件。最終我以一段非常簡練而又能足夠好地運行的一段代碼在其中獲勝。但是,後來,其中一位裁判(Dean Edwards)給出了函數的另一個版本,遠遠地超越了我所編寫的。它的實現使用傳統的方式來附加事件處理函數,完成忽略現代方法。因此,它可以在大量的瀏覽器上運行,而仍然提供了必要的事件的優美性(比如this關鍵字和標準的事件對象)。程序6-14展示的一段示例代碼很好的利用新的addEvent 函數,使用了事件處理的所有的不同側面,包括瀏覽器默認動作的阻止,正確的事件對象的引入,和正確的this關鍵字的引入。

程序6-14. 使用addEvent函數的示例代碼片段

複製內容到剪貼板
代碼:
//等待頁面加載完成
addEvent( window, "load", function(){
    //監視用戶的任何按鍵
    addEvent( document.body, "keypress", function(e){
        //如果用戶按下了Ctrl+Spance
        if ( e.keyCode == 32 && e.ctrlKey ) {
        
            //顯示我們的特別的表單
            this.getElementsByTagName_r("form")[0].style.display = 'block';
            //確保沒有怪異的事情發生
            e.preventDefault();
        }
    });
});

  addEvent函數提供了一個絕妙的簡單而強大的方式來處理DOM事件。只要看看優勢和不足,就可以明顯地看出這一函數可以作爲一致而可靠的方式來處理事件。程序6-15是完整的源代碼,它能在所有的瀏覽器中運行,不泄露任何內存,處理了this關鍵字和事件對象,並標準化了事件對象。

  程序6-15. Dean Edwards編寫的addEvent/removeEvent庫

複製內容到剪貼板
代碼:
// addEvent/removeEvent written by Dean Edwards, 2005
// with input from Tino Zijdel
// http://dean.edwards.name/weblog/2005/10/add-event/
function addEvent(element, type, handler) {
    //爲每一個事件處理函數分派一個唯一的ID
    if (!handler.$$guid) handler.$$guid = addEvent.guid++;
    //爲元素的事件類型創建一個哈希表
    if (!element.events) element.events = {};
    //爲每一個"元素/事件"對創建一個事件處理程序的哈希表
    var handlers = element.events[type];
    if (!handlers) {
        handlers = element.events[type] = {};
        //存儲存在的事件處理函數(如果有)
        if (element["on" + type]) {
            handlers[0] = element["on" + type];
        }
    }
    //將事件處理函數存入哈希表
    handlers[handler.$$guid] = handler;
    //指派一個全局的事件處理函數來做所有的工作
    element["on" + type] = handleEvent;
};
//用來創建唯一的ID的計數器
addEvent.guid = 1;
function removeEvent(element, type, handler) {
    //從哈希表中刪除事件處理函數
    if (element.events && element.events[type]) {
        delete element.events[type][handler.$$guid];
    }
};
function handleEvent(event) {
    var returnValue = true;
    //抓獲事件對象(IE使用全局事件對象)
    event = event || fixEvent(window.event);
    //取得事件處理函數的哈希表的引用
    var handlers = this.events[event.type];
    //執行每一個處理函數
    for (var i in handlers) {
        this.$$handleEvent = handlers[i];
        if (this.$$handleEvent(event) === false) {
            returnValue = false;
        }
    }
    return returnValue;
};
//爲IE的事件對象添加一些“缺失的”函數
function fixEvent(event) {
    //添加標準的W3C方法
    event.preventDefault = fixEvent.preventDefault;
    event.stopPropagation = fixEvent.stopPropagation;
    return event;
};
fixEvent.preventDefault = function() {
    this.returnValue = false;
};
fixEvent.stopPropagation = function() {
    this.cancelBubble = true;
};

addEvent函數的優勢
  1. 它可以在所有瀏覽器上工作,甚至是很老的不被支持的瀏覽器
  2. this關鍵字對所有的綁定的函數可用,指向當前元素
  3. 瀏覽器特有的阻止瀏覽器默認動作和停止事件冒泡的函數都被統一了
  4. 不管是哪種瀏覽器,事件對象總是作爲第一個參數傳給處理函數
addEvent函數的缺點
  1. 它只能工作於冒泡階段(因爲它在底層使用了傳統事件綁定方法)
  考慮到addEvent/removeEvent函數是如此的強大,絕對沒有理由不在你的代碼中使用它們。在Dean的代碼的基礎上,添加一些諸如更好的事件對象標準化、事件觸發、大量事件刪除這類在通常的事件結構中原本極難的事情委實是輕而易舉。

 

事件的類型

  JavaScript事件可以被歸入幾種不同的類別。最常用的類別可能是鼠標交互事件,然後是鍵盤和表單事件。下面的列表提供了web應用程序中存在並可被處理的不同各類的事件的粗略預覽。參考附錄A和附錄B,可以得到大量的事件的實例。
鼠標事件: 分爲兩種,追蹤鼠標當前位置的事件(mouseover,mouseout),和追蹤鼠標在哪兒被點擊的事件(mouseup,mousedown,click)。
鍵盤事件: 負責追蹤鍵盤的按鍵何時以及在何種上下文中(比如說,追蹤一個form元素內的按鍵相對於出現在整個頁面的按鍵)被按下。與鼠標相似,三個事件用來追蹤鍵盤:keyup,keydown,keypress。
UI事件: 用來追蹤用戶何時從頁面的一部分轉到另一部分。例如,使用它你能夠可靠地知道用戶何時開始在一個表單中輸入。用來追蹤這一點的兩個事件是focus和blur(用於對象失去焦點時)。
表單事件: 直接與只發生於表單和表單輸入元素上的交互相關。submit事件用來追蹤表單何時提交;change事件監視用戶向元素的輸入;select事件當<select>元素被更新時觸發。
加 載和錯誤事件: 事件的最後一類是與頁面本身有關的,關注頁面的加載狀態。它們被關聯到何時用戶第一次加載頁面(load事件)和最終離開頁面(unload和 beforeunload事件)。另外,JavaScript錯誤使用error事件追蹤,這給了你以獨立處理錯誤的能力。
  記住這些大致的事件分類,我推薦你積極地查看附錄A和附錄B的材料,其中剖析了所有的常用的事件:它們怎樣工作,在不同的瀏覽器中有着怎樣的差別,並描述了使它們如你所希望的那樣工作所需的所有複雜細節。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章