前端重新學習(15)DOM中的事件(下)

來源 《Javascript高級程序設計》 筆記

目錄

事件類型

UI事件

焦點事件

鼠標與滾輪事件

鍵盤與文本事件

複合事件

變動事件

HTML5事件

設備事件

觸摸與手勢事件

內存和性能

事件委託

移除事件程序

模擬事件

DOM中的事件模擬

1. 模擬鼠標事件

2. 模擬鍵盤事件 

3. 模擬其他事件 

4. 自定義 DOM事件

IE中的事件模擬


事件類型

Web 瀏覽器中可能發生的事件有很多類型。如前所述,不同的事件類型具有不同的信息,而“DOM3 級事件”規定了以下幾類事件。 

  •  UI(User Interface,用戶界面)事件,當用戶與頁面上的元素交互時觸發;
  •  焦點事件,當元素獲得或失去焦點時觸發; 
  •      鼠標事件,當用戶通過鼠標在頁面上執行操作時觸發;
  •  滾輪事件,當使用鼠標滾輪(或類似設備)時觸發; 
  •      文本事件,當在文檔中輸入文本時觸發;
  •  鍵盤事件,當用戶通過鍵盤在頁面上執行操作時觸發;
  •  合成事件,當爲 IME(Input Method Editor,輸入法編輯器)輸入字符時觸發;
  •  變動(mutation)事件,當底層 DOM結構發生變化時觸發。
  •  變動名稱事件,當元素或屬性名變動時觸發。此類事件已經被廢棄,沒有任何瀏覽器實現它們, 因此本章不做介紹。 

除了這幾類事件之外,HTML5也定義了一組事件,而有些瀏覽器還會在 DOM和 BOM中實現其他 專有事件。這些專有的事件一般都是根據開發人員需求定製的,沒有什麼規範,因此不同瀏覽器的實現 有可能不一致。

DOM3 級事件模塊在 DOM2 級事件模塊基礎上重新定義了這些事件,也添加了一些新事件。包括 IE9在內的所有主流瀏覽器都支持 DOM2級事件。IE9也支持 DOM3級事件。 

UI事件

UI事件指的是那些不一定與用戶操作有關的事件。這些事件在 DOM規範出現之前,都是以這種或 那種形式存在的,而在 DOM規範中保留是爲了向後兼容。現有的 UI事件如下。 

  •  DOMActivate:表示元素已經被用戶操作(通過鼠標或鍵盤)激活。這個事件在 DOM3 級事 件中被廢棄,但 Firefox 2+和 Chrome支持它。考慮到不同瀏覽器實現的差異,不建議使用這個 事件。 
  •  load:當頁面完全加載後在 window 上面觸發,當所有框架都加載完畢時在框架集上面觸發, 當圖像加載完畢時在<img>元素上面觸發,或者當嵌入的內容加載完畢時在<object>元素上面 觸發。
  •  unload:當頁面完全卸載後在 window 上面觸發,當所有框架都卸載後在框架集上面觸發,或 者當嵌入的內容卸載完畢後在<object>元素上面觸發。
  •  abort:在用戶停止下載過程時,如果嵌入的內容沒有加載完,則在<object>元素上面觸發。
  •  error:當發生 JavaScript錯誤時在 window 上面觸發,當無法加載圖像時在<img>元素上面觸 發,當無法加載嵌入內容時在<object>元素上面觸發,或者當有一或多個框架無法加載時在框 架集上面觸發。第 17章將繼續討論這個事件。
  •  select:當用戶選擇文本框(<input>或<texterea>)中的一或多個字符時觸發。第 14章將 繼續討論這個事件。
  •  resize:當窗口或框架的大小變化時在 window 或框架上面觸發。
  •  scroll:當用戶滾動帶滾動條的元素中的內容時,在該元素上面觸發。<body>元素中包含所加 載頁面的滾動條。 

多數這些事件都與 window 對象或表單控件相關。 除了DOMActivate 之外,其他事件在DOM2級事件中都歸爲HTML事件(DOMActivate 在DOM2 級中仍然屬於 UI事件)。要確定瀏覽器是否支持 DOM2級事件規定的 HTML事件,可以使用如下代碼:

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

注意,只有根據“DOM2 級事件”實現這些事件的瀏覽器纔會返回 true。而以非標準方式支持這 些事件的瀏覽器則會返回 false。要確定瀏覽器是否支持“DOM3級事件”定義的事件,可以使用如下 代碼: 

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

1. load 事件 

JavaScript 中常用的一個事件就是 load。當頁面完全加載後(包括所有圖像、JavaScript 文件、 CSS文件等外部資源),就會觸發 window 上面的 load 事件。有兩種定義 onload 事件處理程序的方式。 第一種方式是使用如下所示的 JavaScript代碼: 


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

這是通過 JavaScript 來指定事件處理程序的方式,使用了本章前面定義的跨瀏覽器的 EventUtil 對象。與添加其他事件一樣,這裏也給事件處理程序傳入了一個 event 對象。這個 event 對象中不包 含有關這個事件的任何附加信息,但在兼容 DOM 的瀏覽器中,event.target 屬性的值會被設置爲 document,而 IE並不會爲這個事件設置 srcElement 屬性。 第二種指定 onload 事件處理程序的方式是爲<body>元素添加一個 onload 特性,如下面的例子 所示: 

<!DOCTYPE html> 
<html> 
<head> 
 <title>Load Event Example</title>
 </head> 
<body οnlοad="alert('Loaded!')"> 
 
</body> 
</html> 

一般來說,在 window 上面發生的任何事件都可以在<body>元素中通過相應的特性來指定,因爲 在 HTML中無法訪問 window 元素。實際上,這只是爲了保證向後兼容的一種權宜之計,但所有瀏覽器 都能很好地支持這種方式。我們建議讀者儘可能使用 JavaScript方式。 

根據“DOM2級事件”規範,應該在 document 而非 window 上面觸發 load 事 件。但是,所有瀏覽器都在 window 上面實現了該事件,以確保向後兼容。 

圖像上面也可以觸發 load 事件,無論是在 DOM中的圖像元素還是 HTML中的圖像元素。因此, 可以在 HTML中爲任何圖像指定 onload 事件處理程序,例如: 


 <img src="smile.gif" οnlοad="alert('Image loaded.')"> 

這樣,當例子中的圖像加載完畢後就會顯示一個警告框。同樣的功能也可以使用 JavaScript來實現, 例如: 

var image = document.getElementById("myImage");
 EventUtil.addHandler(image, "load", function(event){
     event = EventUtil.getEvent(event);
     alert(EventUtil.getTarget(event).src);
 }); 

這裏,使用 JavaScript指定了 onload 事件處理程序。同時也傳入了 event 對象,儘管它也不包含 什麼有用的信息。不過,事件的目標是<img>元素,因此可以通過 src 屬性訪問並顯示該信息。 在創建新的<img>元素時,可以爲其指定一個事件處理程序,以便圖像加載完畢後給出提示。此時, 重要的是要在指定 src 屬性之前先指定事件,如下面的例子所示。 

EventUtil.addHandler(window, "load", function(){ 
    var image = document.createElement("img");
     EventUtil.addHandler(image, "load", function(event){ 
        event = EventUtil.getEvent(event);
         alert(EventUtil.getTarget(event).src);
     });
     document.body.appendChild(image);
     image.src = "smile.gif"; 
}); 
 

在這個例子中,首先爲 window 指定了 onload 事件處理程序。原因在於,我們是想向 DOM中添 加一個新元素,所以必須確定頁面已經加載完畢——如果在頁面加載前操作 document.body 會導致錯 誤。然後,創建了一個新的圖像元素,並設置了其 onload 事件處理程序。後又將這個圖像添加到頁 面中,還設置了它的 src 屬性。這裏有一點需要格外注意:新圖像元素不一定要從添加到文檔後纔開始 下載,只要設置了 src 屬性就會開始下載

同樣的功能也可以通過使用 DOM0級的 Image 對象實現。在 DOM出現之前,開發人員經常使用 Image 對象在客戶端預先加載圖像。可以像使用<img>元素一樣使用 Image 對象,只不過無法將其添 加到 DOM樹中。下面來看一個例子。 
 

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

在此,我們使用 Image 構造函數創建了一個新圖像的實例,然後又爲它指定了事件處理程序。有 的瀏覽器將 Image 對象實現爲<img>元素,但並非所有瀏覽器都如此,所以好將它們區別對待。

在不屬於 DOM文檔的圖像(包括未添加到文檔的<img>元素和 Image 對象)上 觸發 load 事件時,IE8及之前版本不會生成 event 對象。IE9修復了這個問題。 

還有一些元素也以非標準的方式支持 load 事件。在 IE9+、Firefox、Opera、Chrome和 Safari 3+及 更高版本中,<script>元素也會觸發 load 事件,以便開發人員確定動態加載的 JavaScript文件是否加 載完畢。與圖像不同,只有在設置了<script>元素的 src 屬性並將該元素添加到文檔後,纔會開始下 載 JavaScript 文件。換句話說,對於<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);
}); 

這個例子使用了跨瀏覽器的EventUtil對象爲新創建的<script>元素指定了onload事件處理程 序。此時,大多數瀏覽器中 event 對象的 target 屬性引用的都是<script>節點,而在 Firefox 3之前 的版本中,引用的則是 document。IE8及更早版本不支持<script>元素上的 load 事件。 IE和 Opera還支持<link>元素上的 load 事件,以便開發人員確定樣式表是否加載完畢。例如

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>節點類似,在未指定 href 屬性並將<link>元素添加到文檔之前也不會開始下載樣式表。 

2. unload 事件

與 load 事件對應的是 unload 事件,這個事件在文檔被完全卸載後觸發。只要用戶從一個頁面切 換到另一個頁面,就會發生 unload 事件。而利用這個事件多的情況是清除引用,以避免內存泄漏。 與 load 事件類似,也有兩種指定 onunload 事件處理程序的方式。第一種方式是使用 JavaScript,如 下所示: 

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

此時生成的 event 對象在兼容 DOM的瀏覽器中只包含 target 屬性(值爲 document)。IE8及之 前版本則爲這個事件對象提供了 srcElement 屬性。 

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


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

無論使用哪種方式,都要小心編寫 onunload 事件處理程序中的代碼。既然 unload 事件是在一切 都被卸載之後才觸發,那麼在頁面加載後存在的那些對象,此時就不一定存在了。此時,操作 DOM節 點或者元素的樣式就會導致錯誤。 

根據“DOM2級事件”,應該在<body>元素而非 window 對象上面觸發 unload 事件。不過,所有瀏覽器都在 window 上實現了 unload 事件,以確保向後兼容。 

3. resize 事件 

當瀏覽器窗口被調整到一個新的高度或寬度時,就會觸發 resize 事件。這個事件在 window(窗 口)上面觸發,因此可以通過 JavaScript或者<body>元素中的 onresize 特性來指定事件處理程序。如前所述,我們還是推薦使用如下所示的 JavaScript方式: 

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

與其他發生在 window 上的事件類似,在兼容 DOM的瀏覽器中,傳入事件處理程序中的 event 對 象有一個 target 屬性,值爲 document;而 IE8及之前版本則未提供任何屬性。 

關於何時會觸發 resize 事件,不同瀏覽器有不同的機制。IE、Safari、Chrome和 Opera會在瀏覽 器窗口變化了 1像素時就觸發 resize 事件,然後隨着變化不斷重複觸發。Firefox則只會在用戶停止調 整窗口大小時纔會觸發 resize 事件。由於存在這個差別,應該注意不要在這個事件的處理程序中加入 大計算量的代碼,因爲這些代碼有可能被頻繁執行,從而導致瀏覽器反應明顯變慢。 

瀏覽器窗口小化或大化時也會觸發 resize 事件。 

4. scroll 事件 

雖然 scroll 事件是在 window 對象上發生的,但它實際表示的則是頁面中相應元素的變化。在混 雜模式下,可以通過<body>元素的 scrollLeft 和 scrollTop 來監控到這一變化;而在標準模式下, 除 Safari 之外的所有瀏覽器都會通過<html>元素來反映這一變化(Safari 仍然基於<body>跟蹤滾動位 置),如下面的例子所示:

EventUtil.addHandler(window, "scroll", function(event){
     if (document.compatMode == "CSS1Compat"){
         alert(document.documentElement.scrollTop);
     } else {
         alert(document.body.scrollTop);
     } 
}); 
 

以上代碼指定的事件處理程序會輸出頁面的垂直滾動位置——根據呈現模式不同使用了不同的元 素。由於 Safari 3.1之前的版本不支持 document.compatMode,因此舊版本的瀏覽器就會滿足第二個 條件。 與 resize 事件類似,scroll 事件也會在文檔被滾動期間重複被觸發,所以有必要儘量保持事件 處理程序的代碼簡單。 

 

 

 

焦點事件

焦點事件會在頁面元素獲得或失去焦點時觸發。利用這些事件並與 document.hasFocus()方法及 document.activeElement 屬性配合,可以知曉用戶在頁面上的行蹤。有以下 6個焦點事件。 

  •  blur:在元素失去焦點時觸發。這個事件不會冒泡;所有瀏覽器都支持它。
  •  DOMFocusIn:在元素獲得焦點時觸發。這個事件與 HTML事件 focus 等價,但它冒泡。只有 Opera支持這個事件。DOM3級事件廢棄了 DOMFocusIn,選擇了 focusin。
  •  DOMFocusOut:在元素失去焦點時觸發。這個事件是 HTML事件 blur 的通用版本。只有 Opera 支持這個事件。DOM3級事件廢棄了 DOMFocusOut,選擇了 focusout。 
  •  focus:在元素獲得焦點時觸發。這個事件不會冒泡;所有瀏覽器都支持它。
  •  focusin:在元素獲得焦點時觸發。這個事件與 HTML事件 focus 等價,但它冒泡。支持這個 事件的瀏覽器有 IE5.5+、Safari 5.1+、Opera 11.5+和 Chrome。
  •  focusout:在元素失去焦點時觸發。這個事件是 HTML事件 blur 的通用版本。支持這個事件 的瀏覽器有 IE5.5+、Safari 5.1+、Opera 11.5+和 Chrome。

這一類事件中主要的兩個是 focus 和 blur,它們都是 JavaScript早期就得到所有瀏覽器支持的 事件。這些事件的大問題是它們不冒泡。因此,IE的 focusin 和 focusout 與 Opera的 DOMFocusIn 和 DOMFocusOut纔會發生重疊。IE的方式後被 DOM3級事件採納爲標準方式。 

當焦點從頁面中的一個元素移動到另一個元素,會依次觸發下列事件: 

(1) focusout 在失去焦點的元素上觸發;

(2) focusin 在獲得焦點的元素上觸發;

(3) blur 在失去焦點的元素上觸發;

(4) DOMFocusOut 在失去焦點的元素上觸發;

(5) focus 在獲得焦點的元素上觸發;

(6) DOMFocusIn 在獲得焦點的元素上觸發。 

其中,blur、DOMFocusOut 和 focusout 的事件目標是失去焦點的元素;而 focus、DOMFocusIn 和 focusin 的事件目標是獲得焦點的元素。

要確定瀏覽器是否支持這些事件,可以使用如下代碼: 

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

 即使 focus 和 blur 不冒泡,也可以在捕獲階段偵聽到它們。Peter-Paul Koch就此 寫過一篇非常棒的文章:www.quirksmode.org/blog/archives/2008/04/delegating_the.html。 

 

 

鼠標與滾輪事件

鼠標事件是 Web 開發中常用的一類事件,畢竟鼠標還是主要的定位設備。DOM3 級事件中定 義了 9個鼠標事件,簡介如下。 

  •  click:在用戶單擊主鼠標按鈕(一般是左邊的按鈕)或者按下回車鍵時觸發。這一點對確保 易訪問性很重要,意味着 onclick 事件處理程序既可以通過鍵盤也可以通過鼠標執行。
  •  dblclick:在用戶雙擊主鼠標按鈕(一般是左邊的按鈕)時觸發。從技術上說,這個事件並不 是 DOM2級事件規範中規定的,但鑑於它得到了廣泛支持,所以 DOM3級事件將其納入了標準。
  •   mousedown:在用戶按下了任意鼠標按鈕時觸發。不能通過鍵盤觸發這個事件。
  •  mouseenter:在鼠標光標從元素外部首次移動到元素範圍之內時觸發。這個事件不冒泡,而且 在光標移動到後代元素上不會觸發。DOM2級事件並沒有定義這個事件,但 DOM3級事件將它 納入了規範。IE、Firefox 9+和 Opera支持這個事件。
  •  mouseleave:在位於元素上方的鼠標光標移動到元素範圍之外時觸發。這個事件不冒泡,而且 在光標移動到後代元素上不會觸發。DOM2級事件並沒有定義這個事件,但 DOM3級事件將它 納入了規範。IE、Firefox 9+和 Opera支持這個事件。
  •  mousemove:當鼠標指針在元素內部移動時重複地觸發。不能通過鍵盤觸發這個事件。
  •  mouseout:在鼠標指針位於一個元素上方,然後用戶將其移入另一個元素時觸發。又移入的另 一個元素可能位於前一個元素的外部,也可能是這個元素的子元素。不能通過鍵盤觸發這個事件。
  •   mouseover:在鼠標指針位於一個元素外部,然後用戶將其首次移入另一個元素邊界之內時觸 發。不能通過鍵盤觸發這個事件。
  •  mouseup:在用戶釋放鼠標按鈕時觸發。不能通過鍵盤觸發這個事件。 

頁面上的所有元素都支持鼠標事件。除了 mouseenter 和 mouseleave,所有鼠標事件都會冒泡, 也可以被取消,而取消鼠標事件將會影響瀏覽器的默認行爲。取消鼠標事件的默認行爲還會影響其他事 件,因爲鼠標事件與其他事件是密不可分的關係。 

只有在同一個元素上相繼觸發 mousedown 和 mouseup 事件,纔會觸發 click 事件;如果 mousedown 或 mouseup 中的一個被取消,就不會觸發 click 事件。類似地,只有觸發兩次 click 事 件,纔會觸發一次dblclick事件。如果有代碼阻止了連續兩次觸發click事件(可能是直接取消click 事件,也可能通過取消 mousedown 或 mouseup 間接實現),那麼就不會觸發 dblclick 事件了。這 4 個事件觸發的順序始終如下: 

(1) mousedown

(2) mouseup

(3) click

(4) mousedown

(5) mouseup

(6) click

(7) dblclick 

顯然,click 和 dblclick 事件都會依賴於其他先行事件的觸發;而 mousedown 和 mouseup 則 不受其他事件的影響。 

IE8 及之前版本中的實現有一個小 bug,因此在雙擊事件中,會跳過第二個 mousedown 和 click 事件,其順序如下: 

(1) mousedown

(2) mouseup

(3) click

(4) mouseup

(5) dblclick 

IE9修復了這個 bug,之後順序就正確了。 使用以下代碼可以檢測瀏覽器是否支持以上 DOM2 級事件(除 dbclick、mouseenter 和 mouseleave 之外): 
 

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

 要檢測瀏覽器是否支持上面的所有事件,可以使用以下代碼:

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

注意,DOM3級事件的 feature 名是"MouseEvent",而非"MouseEvents"。 鼠標事件中還有一類滾輪事件。而說是一類事件,其實就是一個 mousewheel 事件。這個事件跟蹤 鼠標滾輪,類似於 Mac的觸控板。 

1. 客戶區座標位置 

鼠標事件都是在瀏覽器視口中的特定位置上發生的。這個位置信息保存在事件對象的 clientX 和 clientY 屬性中。所有瀏覽器都支持這兩個屬性,它們的值表示事件發生時鼠標指針在視口中的水平 和垂直座標。圖 13-4展示了視口中客戶區座標位置的含義。 

可以使用類似下列代碼取得鼠標事件的客戶端座標信息:

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

這裏爲一個<div>元素指定了 onclick 事件處理程序。當用戶單擊這個元素時,就會看到事件的 客戶端座標信息。注意,這些值中不包括頁面滾動的距離,因此這個位置並不表示鼠標在頁面上的位置。

2. 頁面座標位置 
 
 通過客戶區座標能夠知道鼠標是在視口中什麼位置發生的,而頁面座標通過事件對象的 pageX 和 pageY 屬性,能告訴你事件是在頁面中的什麼位置發生的。換句話說,這兩個屬性表示鼠標光標在頁面 中的位置,因此座標是從頁面本身而非視口的左邊和頂邊計算的。

以下代碼可以取得鼠標事件在頁面中的座標: 

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

 在頁面沒有滾動的情況下,pageX 和 pageY 的值與 clientX 和 clientY 的值相等。

IE8 及更早版本不支持事件對象上的頁面座標,不過使用客戶區座標和滾動信息可以計算出來。這 時候需要用到 document.body(混雜模式)或 document.documentElement(標準模式)中的 scrollLeft 和 scrollTop 屬性。計算過程如下所示: 
 

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. 屏幕座標位置 

鼠標事件發生時,不僅會有相對於瀏覽器窗口的位置,還有一個相對於整個電腦屏幕的位置。而通 過 screenX 和 screenY 屬性就可以確定鼠標事件發生時鼠標指針相對於整個屏幕的座標信息。圖 13-5 展示了瀏覽器中屏幕座標的含義。 

 可以使用類似下面的代碼取得鼠標事件的屏幕座標: 

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

}); 

與前一個例子類似,這裏也是爲<div>元素指定了一個 onclick 事件處理程序。當這個元素被單 擊時,就會顯示出事件的屏幕座標信息了。 

4. 修改鍵 

雖然鼠標事件主要是使用鼠標來觸發的,但在按下鼠標時鍵盤上的某些鍵的狀態也可以影響到所要 採取的操作。這些修改鍵就是 Shift、Ctrl、Alt和 Meta(在 Windows鍵盤中是 Windows鍵,在蘋果機中 是 Cmd 鍵),它們經常被用來修改鼠標事件的行爲。DOM 爲此規定了 4 個屬性,表示這些修改鍵的狀 態:shiftKey、ctrlKey、altKey 和 metaKey。這些屬性中包含的都是布爾值,如果相應的鍵被按 下了,則值爲 true,否則值爲 false。當某個鼠標事件發生時,通過檢測這幾個屬性就可以確定用戶 是否同時按下了其中的鍵。來看下面的例子。 

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(",")); 
 
}); 

在這個例子中,我們通過一個 onclick 事件處理程序檢測了不同修改鍵的狀態。數組 keys 中包 含着被按下的修改鍵的名稱。換句話說,如果有屬性值爲 true,就會將對應修改鍵的名稱添加到 keys 數組中。在事件處理程序的後,有一個警告框將檢測到的鍵的信息顯示給用戶。 

IE9、Firefox、Safari、Chrome和 Opera都支持這 4個鍵。IE8及之前版本不支持 metaKey 屬性。 

5. 相關元素 

在發生 mouseover 和 mouserout 事件時,還會涉及更多的元素。這兩個事件都會涉及把鼠標指 針從一個元素的邊界之內移動到另一個元素的邊界之內。對 mouseover 事件而言,事件的主目標是獲 得光標的元素,而相關元素就是那個失去光標的元素。類似地,對 mouseout 事件而言,事件的主目標 是失去光標的元素,而相關元素則是獲得光標的元素。來看下面的例子。 

<!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> 
 

這個例子會在頁面上顯示一個<div>元素。如果鼠標指針一開始位於這個<div>元素上,然後移出 了這個元素,那麼就會在<div>元素上觸發 mouseout 事件,相關元素就是<body>元素。與此同時, <body>元素上面會觸發 mouseover 事件,而相關元素變成了<div>。 

DOM通過 event 對象的 relatedTarget 屬性提供了相關元素的信息。這個屬性只對於 mouseover 和mouseout事件才包含值;對於其他事件,這個屬性的值是null。IE8及之前版本不支持relatedTarget 屬性,但提供了保存着同樣信息的不同屬性。在 mouseover 事件觸發時,IE的 fromElement 屬性中保 存了相關元素;在 mouseout 事件觸發時,IE的 toElement 屬性中保存着相關元素。(IE9支持所有這些 屬性。)可以把下面這個跨瀏覽器取得相關元素的方法添加到 EventUtil 對象中。 
 

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; 
        } 
    }, 
     //省略了其他代碼 
 };

與以前添加的跨瀏覽器方法一樣,這個方法也使用了特性檢測來確定返回哪個值。可以像下面這樣 使用 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);         });  
 

這個例子爲<div>元素的 mouseout 事件註冊了一個事件處理程序。當事件觸發時,會有一個警告 框顯示鼠標移出和移入的元素信息。

6. 鼠標按鈕 

只有在主鼠標按鈕被單擊(或鍵盤回車鍵被按下)時纔會觸發 click 事件,因此檢測按鈕的信息 並不是必要的。但對於 mousedown 和 mouseup 事件來說,則在其 event 對象存在一個 button 屬性, 表示按下或釋放的按鈕。DOM的 button 屬性可能有如下 3個值:0 表示主鼠標按鈕,1 表示中間的鼠 標按鈕(鼠標滾輪按鈕) ,2 表示次鼠標按鈕。在常規的設置中,主鼠標按鈕就是鼠標左鍵,而次鼠標 按鈕就是鼠標右鍵。 

IE8及之前版本也提供了 button 屬性,但這個屬性的值與 DOM的 button 屬性有很大差異。 

 0:表示沒有按下按鈕。

 1:表示按下了主鼠標按鈕。

 2:表示按下了次鼠標按鈕。

 3:表示同時按下了主、次鼠標按鈕。

 4:表示按下了中間的鼠標按鈕。

 5:表示同時按下了主鼠標按鈕和中間的鼠標按鈕。

 6:表示同時按下了次鼠標按鈕和中間的鼠標按鈕。

 7:表示同時按下了三個鼠標按鈕。 

不難想見,DOM模型下的 button 屬性比 IE模型下的 button 屬性更簡單也更爲實用,因爲同時 按下多個鼠標按鈕的情形十分罕見。常見的做法就是將 IE模型規範化爲 DOM方式,畢竟除 IE8及更 早版本之外的其他瀏覽器都原生支持 DOM模型。而對主、中、次按鈕的映射並不困難,只要將 IE的其 他選項分別轉換成如同按下這三個按鍵中的一個即可(同時將主按鈕作爲優先選取的對象)。換句話說, IE中返回的 5 和 7 會被轉換成 DOM模型中的 0。 由於單獨使用能力檢測無法確定差異(兩種模型有同名的 button 屬性),因此必須另闢蹊徑。我 們知道,支持 DOM 版鼠標事件的瀏覽器可以通過 hasFearture()方法來檢測,所以可以再爲 EventUtil 對象添加如下 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; 
            } 
        }  
   }, 
 
    //省略了其他代碼 
 
}; 

通過檢測"MouseEvents"這個特性,就可以確定 event 對象中存在的 button 屬性中是否包含正 確的值。如果測試失敗,說明是 IE,就必須對相應的值進行規範化。以下是使用該方法的示例。

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

在這個例子中,我們爲一個<div>元素添加了一個 onmousedown 事件處理程序。當在這個元素上 按下鼠標按鈕時,會有警告框顯示按鈕的代碼。 

在使用 onmouseup 事件處理程序時,button 的值表示釋放的是哪個按鈕。此 外,如果不是按下或釋放了主鼠標按鈕,Opera 不會觸發 mouseup 或 mousedown 事件

7. 更多的事件信息 

“DOM2級事件”規範在 event 對象中還提供了 detail 屬性,用於給出有關事件的更多信息。對 於鼠標事件來說,detail 中包含了一個數值,表示在給定位置上發生了多少次單擊。在同一個元素上 相繼地發生一次 mousedown 和一次 mouseup 事件算作一次單擊。detail 屬性從 1 開始計數,每次單 擊發生後都會遞增。如果鼠標在 mousedown 和mouseup 之間移動了位置,則 detail 會被重置爲 0。 

IE也通過下列屬性爲鼠標事件提供了更多信息。 

  • altLeft:布爾值,表示是否按下了Alt鍵。如果 altLeft 的值爲 true,則altKey 的值也爲 true。
  • ctrlLeft:布爾值,表示是否按下了 Ctrl鍵。如果 ctrlLeft 的值爲 true,則 ctrlKey 的值 也爲 true。
  • offsetX:光標相對於目標元素邊界的 x座標。 
  • offsetY:光標相對於目標元素邊界的 y座標。
  •  shiftLeft:布爾值,表示是否按下了 Shift鍵。如果 shiftLeft 的值爲 true,則 shiftKey 的值也爲 true。

這些屬性的用處並不大,原因一方面是隻有 IE 支持它們,另一方是它們提供的信息要麼沒有什麼 價值,要麼可以通過其他方式計算得來。 

8. 鼠標滾輪事件 

IE 6.0首先實現了 mousewheel 事件。此後,Opera、Chrome和 Safari也都實現了這個事件。當用 戶通過鼠標滾輪與頁面交互、在垂直方向上滾動頁面時(無論向上還是向下),就會觸發 mousewheel 事件。這個事件可以在任何元素上面觸發,終會冒泡到 document(IE8)或 window(IE9、Opera、 Chrome及 Safari)對象。與 mousewheel 事件對應的 event 對象除包含鼠標事件的所有標準信息外, 還包含一個特殊的 wheelDelta 屬性。當用戶向前滾動鼠標滾輪時,wheelDelta 是 120的倍數;當用 戶向後滾動鼠標滾輪時,wheelDelta 是120的倍數。圖 13-6展示了這個屬性。 

 將 mousewheel 事件處理程序指定給頁面中的任何元素或 document 對象,即可處理鼠標滾輪的 交互操作。來看下面的例子。 

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

這個例子會在發生 mousewheel 事件時顯示 wheelDelta 的值。多數情況下,只要知道鼠標滾輪 滾動的方向就夠了,而這通過檢測 wheelDelta 的正負號就可以確定。

有一點要注意:在 Opera 9.5之前的版本中,wheelDelta 值的正負號是顛倒的。如果你打算支持 早期的 Opera版本,就需要使用瀏覽器檢測技術來確定實際的值,如下面的例子所示。 
 

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

以上代碼使用第 9章創建的 client 對象檢測了瀏覽器是不是早期版本的 Opera。 

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

Firefox支持一個名爲 DOMMouseScroll 的類似事件,也是在鼠標滾輪滾動時觸發。與 mousewheel 事件一樣,DOMMouseScroll 也被視爲鼠標事件,因而包含與鼠標事件有關的所有屬性。而有關鼠標滾 輪的信息則保存在 detail 屬性中,當向前滾動鼠標滾輪時,這個屬性的值是-3 的倍數,當向後滾動 鼠標滾輪時,這個屬性的值是 3 的倍數。圖 13-7展示了這個屬性。 
 

可以將 DOMMouseScroll 事件添加到頁面中的任何元素,而且該事件會冒泡到 window 對象。因 此,可以像下面這樣針對這個事件來添加事件處理程序。 

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

這個簡單的事件處理程序會在鼠標滾輪滾動時顯示 detail 屬性的值。 若要給出跨瀏覽器環境下的解決方案,第一步就是創建一個能夠取得鼠標滾輪增量值(delta)的方 法。下面是我們添加到 EventUtil 對象中的這個方法。 

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()方法首先檢測了事件對象是否包含 wheelDelta 屬性,如果是則通過瀏 覽器檢測代碼確定正確的值。如果 wheelDelta 不存在,則假設相應的值保存在 detail 屬性中。由於 Firefox 的值有所不同,因此首先要將這個值的符號反向,然後再乘以 40,就可以保證與其他瀏覽器的 值相同了。有了這個方法之後,就可以將相同的事件處理程序指定給 mousewheel 和 DOMMouse- Scroll 事件了,例如: 
 

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

我們將相關代碼放在了一個私有作用域中,從而不會讓新定義的函數干擾全局作用域。這裏定義的 handleMouseWheel()函數可以用作兩個事件的處理程序(如果指定的事件不存在,則爲該事件指定處 理程序的代碼就會靜默地失敗)。由於使用了 EventUtil.getWheelDelta()方法,我們定義的這個事 件處理程序函數可以適用於任何一種情況。 

9. 觸摸設備 

iOS和 Android設備的實現非常特別,因爲這些設備沒有鼠標。在面向 iPhone和 iPod中的 Safari 開發時,要記住以下幾點。 

  •  不支持 dblclick 事件。雙擊瀏覽器窗口會放大畫面,而且沒有辦法改變該行爲。
  •  輕擊可單擊元素會觸發 mousemove 事件。如果此操作會導致內容變化,將不再有其他事件發生; 如果屏幕沒有因此變化,那麼會依次發生 mousedown、mouseup 和 click 事件。輕擊不可單 擊的元素不會觸發任何事件。可單擊的元素是指那些單擊可產生默認操作的元素(如鏈接),或 者那些已經被指定了 onclick 事件處理程序的元素。
  •  mousemove 事件也會觸發 mouseover 和 mouseout 事件。
  •  兩個手指放在屏幕上且頁面隨手指移動而滾動時會觸發 mousewheel 和 scroll 事件。 

10. 無障礙性問題 

如果你的 Web 應用程序或網站要確保殘疾人特別是那些使用屏幕閱讀器的人都能訪問,那麼在使 用鼠標事件時就要格外小心。前面提到過,可以通過鍵盤上的回車鍵來觸發 click 事件,但其他鼠標 事件卻無法通過鍵盤來觸發。爲此,我們不建議使用 click 之外的其他鼠標事件來展示功能或引發代碼執行。因爲這樣會給盲人或視障用戶造成極大不便。以下是在使用鼠標事件時應當注意的幾個易訪問 性問題。 

  •  使用 click 事件執行代碼。有人指出通過 onmousedown 執行代碼會讓人覺得速度更快,對視 力正常的人來說這是沒錯的。但是,在屏幕閱讀器中,由於無法觸發 mousedown 事件,結果就 會造成代碼無法執行。
  •  不要使用 onmouseover 向用戶顯示新的選項。原因同上,屏幕閱讀器無法觸發這個事件。如果 確實非要通過這種方式來顯示新選項,可以考慮添加顯示相同信息的鍵盤快捷方式。
  •  不要使用 dblclick 執行重要的操作。鍵盤無法觸發這個事件。 遵照以上提示可以極大地提升殘疾人在訪問你的 Web 應用程序或網站時的易訪問性。 

要了解如何在網頁中實現無障礙訪問的內容,請訪問 www.webaim.org 和 http://yaccessibilityblog.com/。 

鍵盤與文本事件

用戶在使用鍵盤時會觸發鍵盤事件。“DOM2 級事件”初規定了鍵盤事件,但在終定稿之前又 刪除了相應的內容。

結果,對鍵盤事件的支持主要遵循的是 DOM0級。 “DOM3級事件”爲鍵盤事件制定了規範,IE9率先完全實現了該規範。其他瀏覽器也在着手實現這 一標準,但仍然有很多遺留的問題。

有 3個鍵盤事件,簡述如下。 

  •  keydown:當用戶按下鍵盤上的任意鍵時觸發,而且如果按住不放的話,會重複觸發此事件。
  •  keypress:當用戶按下鍵盤上的字符鍵時觸發,而且如果按住不放的話,會重複觸發此事件。 按下 Esc鍵也會觸發這個事件。Safari 3.1之前的版本也會在用戶按下非字符鍵時觸發 keypress 事件。
  •  keyup:當用戶釋放鍵盤上的鍵時觸發。 

雖然所有元素都支持以上 3個事件,但只有在用戶通過文本框輸入文本時才常用到。 只有一個文本事件:textInput。這個事件是對 keypress 的補充,用意是在將文本顯示給用戶之 前更容易攔截文本。

在文本插入文本框之前會觸發 textInput 事件。 在用戶按了一下鍵盤上的字符鍵時,首先會觸發 keydown 事件,然後緊跟着是 keypress 事件, 後會觸發 keyup 事件。其中,keydown 和 keypress 都是在文本框發生變化之前被觸發的;而 keyup 事件則是在文本框已經發生變化之後被觸發的。如果用戶按下了一個字符鍵不放,就會重複觸發 keydown 和 keypress 事件,直到用戶鬆開該鍵爲止。

如果用戶按下的是一個非字符鍵,那麼首先會觸發 keydown 事件,然後就是 keyup 事件。如果按 住這個非字符鍵不放,那麼就會一直重複觸發 keydown 事件,直到用戶鬆開這個鍵,此時會觸發 keyup 事件。

鍵盤事件與鼠標事件一樣,都支持相同的修改鍵。而且,鍵盤事件的事件對象中 也有 shiftKey、ctrlKey、altKey 和 metaKey 屬性。IE不支持 metaKey。 

 1. 鍵碼 

在發生 keydown 和 keyup 事件時,event 對象的 keyCode 屬性中會包含一個代碼,與鍵盤上一 個特定的鍵對應。對數字字母字符鍵,keyCode 屬性的值與 ASCII 碼中對應小寫字母或數字的編碼相 同。因此,數字鍵 7的 keyCode 值爲 55,而字母 A鍵的 keyCode 值爲 65——與 Shift鍵的狀態無關。 DOM和 IE的 event 對象都支持 keyCode 屬性。請看下面這個例子: 
 

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

在這個例子中,用戶每次在文本框中按鍵觸發 keyup 事件時,都會顯示 keyCode 的值。下表列出 了所有非字符鍵的鍵碼。 

 

無論keydown或keyup事件都會存在的一些特殊情況。在Firefox和Opera中,按分號鍵時keyCode 值爲 59,也就是 ASCII中分號的編碼;但 IE和 Safari返回 186,即鍵盤中按鍵的鍵碼。 

2. 字符編碼 

發生 keypress 事件意味着按下的鍵會影響到屏幕中文本的顯示。在所有瀏覽器中,按下能夠插入 或刪除字符的鍵都會觸發 keypress 事件;按下其他鍵能否觸發此事件因瀏覽器而異。由於截止到 2008 年,尚無瀏覽器實現“DOM3級事件”規範,所以瀏覽器之間的鍵盤事件並沒有多大的差異。 IE9、Firefox、Chrome 和 Safari的 event 對象都支持一個 charCode 屬性,這個屬性只有在發生 keypress 事件時才包含值,而且這個值是按下的那個鍵所代表字符的 ASCII 編碼。

此時的 keyCode 通常等於0或者也可能等於所按鍵的鍵碼。IE8及之前版本和Opera則是在keyCode中保存字符的ASCII 編碼。要想以跨瀏覽器的方式取得字符編碼,必須首先檢測 charCode 屬性是否可用,如果不可用則使 用 keyCode,如下面的例子所示。 

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

這個方法首先檢測 charCode 屬性是否包含數值(在不支持這個屬性的瀏覽器中,值爲 undefined), 如果是,則返回該值。否則,就返回 keyCode 屬性值。下面是使用這個方法的示例。 

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

在取得了字符編碼之後,就可以使用 String.fromCharCode()將其轉換成實際的字符

3. DOM3級變化 

儘管所有瀏覽器都實現了某種形式的鍵盤事件,DOM3級事件還是做出了一些改變。比如,DOM3 級事件中的鍵盤事件,不再包含 charCode 屬性,而是包含兩個新屬性:key 和 char。

其中,key 屬性是爲了取代 keyCode 而新增的,它的值是一個字符串。在按下某個字符鍵時,key 的值就是相應的文本字符(如“k”或“M”);在按下非字符鍵時, key 的值是相應鍵的名(如“Shift” 或“Down”)。

而 char 屬性在按下字符鍵時的行爲與 key 相同,但在按下非字符鍵時值爲 null。 IE9支持 key 屬性,但不支持 char 屬性。Safari 5和 Chrome支持名爲 keyIdentifier 的屬性, 在按下非字符鍵(例如 Shift)的情況下與 key 的值相同。對於字符鍵,keyIdentifier 返回一個格式 類似“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(identifi er);
     } 
}); 

由於存在跨瀏覽器問題,因此本書不推薦使用 key、keyIdentifier 或 char。 DOM3級事件還添加了一個名爲 location 的屬性,這是一個數值,表示按下了什麼位置上的鍵: 0表示默認鍵盤,1表示左側位置(例如左位的 Alt鍵),2表示右側位置(例如右側的 Shift鍵),3表示 數字小鍵盤,4表示移動設備鍵盤(也就是虛擬鍵盤),5表示手柄(如任天堂 Wii控制器)。IE9支持這 個屬性。Safari和 Chrome支持名爲 keyLocation 的等價屬性,但即有 bug——值始終是 0,除非按下 了數字鍵盤(此時,值 爲 3);否則,不會是 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 的瀏覽器也不多,所以在跨瀏覽器開發中不推薦使用。 後是給 event 對象添加了 getModifierState()方法。這個方法接收一個參數,即等於 Shift、 Control、AltGraph 或 Meta 的字符串,表示要檢測的修改鍵。如果指定的修改鍵是活動的(也就是 處於被按下的狀態),這個方法返回 true,否則返回 false。

 

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

實際上,通過 event 對象的 shiftKey、altKey、ctrlKey 和 metaKey 屬性已經可以取得類似 的屬性了。IE9是唯一支持 getModifierState()方法的瀏覽器。 

4. textInput 事件 

“DOM3級事件”規範中引入了一個新事件,名叫 textInput。根據規範,當用戶在可編輯區域中 輸入字符時,就會觸發這個事件。這個用於替代 keypress 的 textInput 事件的行爲稍有不同。區別 之一就是任何可以獲得焦點的元素都可以觸發keypress事件,但只有可編輯區域才能觸發textInput 事件。

區別之二是 textInput 事件只會在用戶按下能夠輸入實際字符的鍵時纔會被觸發,而 keypress 事件則在按下那些能夠影響文本顯示的鍵時也會觸發(例如退格鍵)。 由於 textInput 事件主要考慮的是字符,因此它的 event 對象中還包含一個 data 屬性,這個屬 性的值就是用戶輸入的字符(而非字符編碼)。

換句話說,用戶在沒有按上檔鍵的情況下按下了 S 鍵, data 的值就是"s",而如果在按住上檔鍵時按下該鍵,data 的值就是"S"。 以下是一個使用 textInput 事件的例子: 
 

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

在這個例子中,插入到文本框中的字符會通過一個警告框顯示出來。 另外,event 對象上還有一個屬性,叫 inputMethod,表示把文本輸入到文本框中的方式。 

 0,表示瀏覽器不確定是怎麼輸入的。

 1,表示是使用鍵盤輸入的。

 2,表示文本是粘貼進來的。

 3,表示文本是拖放進來的。

 4,表示文本是使用 IME輸入的。

 5,表示文本是通過在表單中選擇某一項輸入的。

 6,表示文本是通過手寫輸入的(比如使用手寫筆)。

 7,表示文本是通過語音輸入的。

 8,表示文本是通過幾種方法組合輸入的。

 9,表示文本是通過腳本輸入的。 

使用這個屬性可以確定文本是如何輸入到控件中的,從而可以驗證其有效性。支持 textInput 屬 性的瀏覽器有 IE9+、Safari和 Chrome。只有 IE支持 inputMethod 屬性。 

5. 設備中的鍵盤事件 

任天堂 Wii會在用戶按下 Wii遙控器上的按鍵時觸發鍵盤事件。儘管沒有辦法訪問 Wii遙控器中的 所有按鍵,但還是有一些鍵可以觸發鍵盤事件。圖 13-6 展示了一些鍵的鍵碼,通過這些鍵碼可以知道 用戶按下了哪個鍵。 

當用戶按下十字鍵盤(鍵碼爲 175~178)、減號(170)、加號(174)、1(172)或 2(173)鍵時就 會觸發鍵盤事件。但沒有辦法得知用戶是否按下了電源開關、A、B或主頁鍵。 

iOS版 Safari和 Android版 WebKit 在使用屏幕鍵盤時會觸發鍵盤事件

複合事件

 

複合事件(composition event)是 DOM3級事件中新添加的一類事件,用於處理 IME 的輸入序列。 IME(Input Method Editor,輸入法編輯器)可以讓用戶輸入在物理鍵盤上找不到的字符。例如,使用拉 丁文鍵盤的用戶通過 IME 照樣能輸入日文字符。IME 通常需要同時按住多個鍵,但終只輸入一個字 符。複合事件就是針對檢測和處理這種輸入而設計的。有以下三種複合事件。 

  •  compositionstart:在 IME的文本複合系統打開時觸發,表示要開始輸入了。
  •  compositionupdate:在向輸入字段中插入新字符時觸發。
  •  compositionend:在 IME的文本複合系統關閉時觸發,表示返回正常鍵盤輸入狀態。 複合事件與文本事件在很多方面都很相似。在觸發複合事件時,目標是接收文本的輸入字段。但它 比文本事件的事件對象多一個屬性 data,其中包含以下幾個值中的一個:
  •  如果在 compositionstart 事件發生時訪問,包含正在編輯的文本(例如,已經選中的需要馬 上替換的文本);
  •  如果在 compositionupdate 事件發生時訪問,包含正插入的新字符;
  •  如果在 compositionend 事件發生時訪問,包含此次輸入會話中插入的所有字符。 

與文本事件一樣,必要時可以利用複合事件來篩選輸入。可以像下面這樣使用它們: 

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 年唯一支持複合事件的瀏覽器。由於缺少支持,對於需要開發跨瀏覽器應用的開發 人員,它的用處不大。要確定瀏覽器是否支持複合事件,可以使用以下代碼: 

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

 

變動事件

DOM2級的變動(mutation)事件能在 DOM中的某一部分發生變化時給出提示。變動事件是爲 XML 或 HTML DOM設計的,並不特定於某種語言。DOM2級定義瞭如下變動事件。 

  •  DOMSubtreeModified:在 DOM 結構中發生任何變化時觸發。這個事件在其他任何事件觸發 後都會觸發。
  •  DOMNodeInserted:在一個節點作爲子節點被插入到另一個節點中時觸發。
  •  DOMNodeRemoved:在節點從其父節點中被移除時觸發。
  •  DOMNodeInsertedIntoDocument:在一個節點被直接插入文檔或通過子樹間接插入文檔之後 觸發。這個事件在 DOMNodeInserted之後觸發。
  •  DOMNodeRemovedFromDocument:在一個節點被直接從文檔中移除或通過子樹間接從文檔中移 除之前觸發。這個事件在 DOMNodeRemoved 之後觸發。
  •  DOMAttrModified:在特性被修改之後觸發。
  •  DOMCharacterDataModified:在文本節點的值發生變化時觸發。 

使用下列代碼可以檢測出瀏覽器是否支持變動事件: 

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

 IE8及更早版本不支持任何變動事件。下表列出了不同瀏覽器對不同變動事件的支持情況。 

由於 DOM3級事件模塊作廢了很多變動事件,所以本節只介紹那些將來仍然會得到支持的事件。 

1. 刪除節點 

在使用removeChild()或replaceChild()從DOM中刪除節點時,首先會觸發DOMNodeRemoved 事件。這個事件的目標(event.target)是被刪除的節點,而 event.relatedNode 屬性中包含着對 目標節點父節點的引用。在這個事件觸發時,節點尚未從其父節點刪除,因此其 parentNode 屬性仍然 指向父節點(與 event.relatedNode 相同)。這個事件會冒泡,因而可以在 DOM的任何層次上面處 理它。

如果被移除的節點包含子節點,那麼在其所有子節點以及這個被移除的節點上會相繼觸發 DOMNodeRemovedFromDocument 事件。但這個事件不會冒泡,所以只有直接指定給其中一個子節點的 事件處理程序纔會被調用。這個事件的目標是相應的子節點或者那個被移除的節點,除此之外 event 對象中不包含其他信息。 

 緊隨其後觸發的是 DOMSubtreeModified 事件。這個事件的目標是被移除節點的父節點;此時的 event 對象也不會提供與事件相關的其他信息。 

爲了理解上述事件的觸發過程,下面我們就以一個簡單的 HTML頁面爲例。 

<! 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>

 在這個例子中,我們假設要移除<ul>元素。此時,就會依次觸發以下事件。 

(1) 在<ul>元素上觸發 DOMNodeRemoved 事件。relatedNode 屬性等於 document.body。

(2) 在<ul>元素上觸發 DOMNodeRemovedFromDocument 事件。

(3) 在身爲<ul>元素子節點的每個<li>元素及文本節點上觸發 DOMNodeRemovedFromDocument 事件。

(4) 在 document.body 上觸發 DOMSubtreeModified 事件,因爲<ul>元素是 document.body 的直接子元素。 

運行下列代碼可以驗證以上事件發生的順序。 

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 添加了針對 DOMSubtreeModified 和 DOMNodeRemoved 事件的處理程序, 以便在頁面上處理這些事件。由於 DOMNodeRemovedFromDocument 不會冒泡,所以我們將針對它的 事件處理程序直接添加給了<ul>元素的第一個子節點(在兼容 DOM 的瀏覽器中是一個文本節點)。在 設置了以上事件處理程序後,代碼從文檔中移除了<ul>元素。 

2. 插入節點 

在使用 appendChild()、replaceChild()或 insertBefore()向 DOM中插入節點時,首先會 觸發 DOMNodeInserted 事件。這個事件的目標是被插入的節點,而 event.relatedNode 屬性中包含 一個對父節點的引用。在這個事件觸發時,節點已經被插入到了新的父節點中。這個事件是冒泡的,因 此可以在 DOM的各個層次上處理它。 

緊接着,會在新插入的節點上面觸發 DOMNodeInsertedIntoDocument 事件。這個事件不冒泡, 因此必須在插入節點之前爲它添加這個事件處理程序。這個事件的目標是被插入的節點,除此之外 event 對象中不包含其他信息。 後一個觸發的事件是 DOMSubtreeModified,觸發於新插入節點的父節點。 我們仍以前面的 HTML文檔爲例,可以通過下列 JavaScript代碼來驗證上述事件的觸發順序。 

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

以上代碼首先創建了一個包含文本"Item 4"的新<li>元素。由於 DOMSubtreeModified 和 DOMNodeInserted 事件是冒泡的,所以把它們的事件處理程序添加到了文檔中。在將列表項插入到其 父節點之前,先將 DOMNodeInsertedIntoDocument 事件的事件處理程序添加給它。後一步就是使 用 appendChild()來添加這個列表項;此時,事件開始依次被觸發。首先是在新<li>元素項上觸發 DOMNodeInserted 事件,其 relatedNode 是<ul>元素。然後是觸發新<li>元素上的 DOMNode- InsertedIntoDocument 事件,後觸發的是<ul>元素上的 DOMSubtreeModified 事件。 

HTML5事件

DOM 規範沒有涵蓋所有瀏覽器支持的所有事件。很多瀏覽器出於不同的目的——滿足用戶需求或 解決特殊問題,還實現了一些自定義的事件。HTML5 詳盡列出了瀏覽器應該支持的所有事件。本節只 討論其中得到瀏覽器完善支持的事件,但並非全部事件。(其他事件會在本書其他章節討論。) 

1. contextmenu 事件 

Windows 95在 PC中引入了上下文菜單的概念,即通過單擊鼠標右鍵可以調出上下文菜單。不久, 這個概念也被引入了 Web 領域。爲了實現上下文菜單,開發人員面臨的主要問題是如何確定應該顯示 上下文菜單(在 Windows 中,是右鍵單擊;在 Mac 中,是 Ctrl+單擊),以及如何屏蔽與該操作關聯的 默認上下文菜單。爲解決這個問題,就出現了 contextmenu 這個事件,用以表示何時應該顯示上下文 菜單,以便開發人員取消默認的上下文菜單而提供自定義的菜單。

由於 contextmenu 事件是冒泡的,因此可以爲 document 指定一個事件處理程序,用以處理頁面 中發生的所有此類事件。這個事件的目標是發生用戶操作的元素。在所有瀏覽器中都可以取消這個事件: 在兼容 DOM的瀏覽器中,使用 event.preventDefalut();在 IE中,將 event.returnValue 的值 設置爲 false。因爲 contextmenu 事件屬於鼠標事件,所以其事件對象中包含與光標位置有關的所有 屬性。通常使用 contextmenu 事件來顯示自定義的上下文菜單,而使用 onclick 事件處理程序來隱 藏該菜單。以下面的 HTML頁面爲例。 

<!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 href="http://www.nczonline.net">Nicholas’ site</a></li>
         <li><a href="http://www.wrox.com">Wrox site</a></li>
         <li><a href="http://www.yahoo.com">Yahoo!</a></li>
     </ul>
 </body>
 </html>

這裏的<div>元素包含一個自定義的上下文菜單。其中,<ul>元素作爲自定義上下文菜單,並且在 初始時是隱藏的。實現這個例子的 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";
     });
 }); 
 

在這個例子中,我們爲<div>元素添加了 oncontextmenu 事件的處理程序。這個事件處理程序首 先會取消默認行爲,以保證不顯示瀏覽器默認的上下文菜單。然後,再根據 event 對象 clientX 和 clientY 屬性的值,來確定放置<ul>元素的位置。後一步就是通過將 visibility 屬性設置爲 "visible"來顯示自定義上下文菜單。另外,還爲 document 添加了一個 onclick 事件處理程序,以 便用戶能夠通過鼠標單擊來隱藏菜單(單擊也是隱藏系統上下文菜單的默認操作)。 雖然這個例子很簡單,但它卻展示了 Web 上所有自定義上下文菜單的基本結構。只需爲這個例子 中的上下文菜單添加一些 CSS樣式,就可以得到非常棒的效果。 

支持 contextmenu 事件的瀏覽器有 IE、Firefox、Safari、Chrome和 Opera 11+。

2. beforeunload 事件  

之所以有發生在 window 對象上的 beforeunload 事件,是爲了讓開發人員有可能在頁面卸載前 阻止這一操作。這個事件會在瀏覽器卸載頁面之前觸發,可以通過它來取消卸載並繼續使用原有頁面。 但是,不能徹底取消這個事件,因爲那就相當於讓用戶無法離開當前頁面了。爲此,這個事件的意圖是 將控制權交給用戶。顯示的消息會告知用戶頁面行將被卸載(正因爲如此纔會顯示這個消息),詢問用 戶是否真的要關閉頁面,還是希望繼續留下來(見圖 13-9)。 
 

爲了顯示這個彈出對話框,必須將 event.returnValue 的值設置爲要顯示給用戶的字符串(對 IE及 Fiefox而言),同時作爲函數的值返回(對 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 事件,也都會彈出這個對話框詢問用戶是否 真想離開。Opera 11及之前的版本不支持 beforeunload 事件。 

3. DOMContentLoaded 事件 

如前所述,window 的 load 事件會在頁面中的一切都加載完畢時觸發,但這個過程可能會因爲要 加載的外部資源過多而頗費周折。而 DOMContentLoaded 事件則在形成完整的 DOM樹之後就會觸發, 不理會圖像、JavaScript 文件、CSS 文件或其他資源是否已經下載完畢。與 load 事件不同, DOMContentLoaded 支持在頁面下載的早期添加事件處理程序,這也就意味着用戶能夠儘早地與頁面 進行交互。 要處理 DOMContentLoaded 事件,可以爲 document 或 window 添加相應的事件處理程序(儘管 這個事件會冒泡到 window,但它的目標實際上是 document)。來看下面的例子。 

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

 DOMContentLoaded 事件對象不會提供任何額外的信息(其 target 屬性是 document)。 IE9+、Firefox、Chrome、Safari 3.1+和 Opera 9+都支持 DOMContentLoaded 事件,通常這個事件 既可以添加事件處理程序,也可以執行其他 DOM操作。這個事件始終都會在 load 事件之前觸發。 對於不支持 DOMContentLoaded 的瀏覽器,我們建議在頁面加載期間設置一個時間爲 0毫秒的超 時調用,如下面的例子所示。 
 

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

這段代碼的實際意思就是:“在當前 JavaScript 處理完成後立即運行這個函數。”在頁面下載和構建 期間,只有一個 JavaScript 處理過程,因此超時調用會在該過程結束時立即觸發。至於這個時間與 DOMContentLoaded 被觸發的時間能否同步,主要還是取決於用戶使用的瀏覽器和頁面中的其他代碼。 爲了確保這個方法有效,必須將其作爲頁面中的第一個超時調用;即便如此,也還是無法保證在所有環 境中該超時調用一定會早於 load 事件被觸發。 

4. readystatechange 事件 

IE爲 DOM文檔中的某些部分提供了 readystatechange 事件。這個事件的目的是提供與文檔或 元素的加載狀態有關的信息,但這個事件的行爲有時候也很難預料。支持 readystatechange 事件的 每個對象都有一個 readyState 屬性,可能包含下列 5個值中的一個。

  •  uninitialized(未初始化):對象存在但尚未初始化。 
  •  loading(正在加載):對象正在加載數據。
  •  loaded(加載完畢):對象加載數據完成。
  •  interactive(交互):可以操作對象了,但還沒有完全加載。
  •  complete(完成):對象已經加載完畢。 

這些狀態看起來很直觀,但並非所有對象都會經歷 readyState 的這幾個階段。換句話說,如果某 個階段不適用某個對象,則該對象完全可能跳過該階段;並沒有規定哪個階段適用於哪個對象。顯然, 這意味着 readystatechange 事件經常會少於 4次,而 readyState 屬性的值也不總是連續的。 

對於 document 而言,值爲"interactive"的 readyState 會在與 DOMContentLoaded 大致相 同的時刻觸發 readystatechange 事件。此時,DOM樹已經加載完畢,可以安全地操作它了,因此就 會進入交互(interactive)階段。但與此同時,圖像及其他外部文件不一定可用。下面來看一段處理 readystatechange 事件的代碼。 
 

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

這個事件的 event 對象不會提供任何信息,也沒有目標對象。 在與 load 事件一起使用時,無法預測兩個事件觸發的先後順序。在包含較多或較大的外部資源的 頁面中,會在 load 事件觸發之前先進入交互階段;而在包含較少或較小的外部資源的頁面中,則很難 說 readystatechange 事件會發生在 load 事件前面。 讓問題變得更復雜的是,交互階段可能會早於也可能會晚於完成階段出現,無法確保順序。在包含 較多外部資源的頁面中,交互階段更有可能早於完成階段出現;而在頁面中包含較少外部資源的情況下, 完成階段先於交互階段出現的可能性更大。因此,爲了儘可能搶到先機,有必要同時檢測交互和完成階 段,如下面的例子所示。 

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

對於上面的代碼來說,當 readystatechange 事件觸發時,會檢測 document.readyState 的值, 看當前是否已經進入交互階段或完成階段。如果是,則移除相應的事件處理程序以免在其他階段再執行。 注意,由於事件處理程序使用的是匿名函數,因此這裏使用了 arguments.callee 來引用該函數。然 後,會顯示一個警告框,說明內容已經加載完畢。這樣編寫代碼可以達到與使用 DOMContentLoaded 十分相近的效果。 

支持 readystatechange 事件的瀏覽器有 IE、Firfox 4+和 Opera。 

雖然使用 readystatechange 可以十分近似地模擬 DOMContentLoaded 事件, 但它們本質上還是不同的。在不同頁面中,load 事件與 readystatechange 事件並 不能保證以相同的順序觸發。 

 另外, <script>(在 IE和 Opera中)和<link>(僅 IE中)元素也會觸發 readystatechange 事件,可以用來確定外部的 JavaScript和 CSS文件是否已經加載完成。與在其他瀏覽器中一樣,除非把 動態創建的元素添加到頁面中,否則瀏覽器不會開始下載外部資源。基於元素觸發的 readystatechange 事件也存在同樣的問題,即 readyState 屬性無論等於"loaded"還是 "complete"都可以表示資源已經可用。有時候,readyState 會停在"loaded"階段而永遠不會“完成”; 有時候,又會跳過"loaded"階段而直接“完成”。於是,還需要像對待 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);
 }); 
 

這個例子爲新創建的<script>節點指定了一個事件處理程序。事件的目標是該節點本身,因此當 觸發 readystatechange 事件時,要檢測目標的 readyState 屬性是不是等於"loaded"或 "complete"。如果進入了其中任何一個階段,則移除事件處理程序(以防止被執行兩次),並顯示一個 警告框。與此同時,就可以執行已經加載完畢的外部文件中的函數了。 同樣的編碼方式也適用於通過<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);
 }); 
 

同樣,重要的是要一併檢測 readyState 的兩個狀態,並在調用了一次事件處理程序後就將其移除。

5. pageshow 和 pagehide 事件 

Firefox 和 Opera 有一個特性,名叫“往返緩存”(back-forward cache,或 bfcache),可以在用戶使 用瀏覽器的“後退”和“前進”按鈕時加快頁面的轉換速度。這個緩存中不僅保存着頁面數據,還保存 了 DOM和 JavaScript的狀態;實際上是將整個頁面都保存在了內存裏。如果頁面位於 bfcache中,那麼 再次打開該頁面時就不會觸發 load 事件。儘管由於內存中保存了整個頁面的狀態,不觸發 load 事件 也不應該會導致什麼問題,但爲了更形象地說明 bfcache的行爲,Firefox還是提供了一些新事件。 第一個事件就是 pageshow,這個事件在頁面顯示時觸發,無論該頁面是否來自 bfcache。在重新加 載的頁面中,pageshow 會在 load 事件觸發後觸發;而對於 bfcache中的頁面,pageshow 會在頁面狀 態完全恢復的那一刻觸發。另外要注意的是,雖然這個事件的目標是 document,但必須將其事件處理 程序添加到 window。來看下面的例子。 

(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 進入全局作用域。當頁面首次加載完成時, showCount 的值爲 0。此後,每當觸發 pageshow 事件,showCount 的值就會遞增並通過警告框顯示 出來。如果你在離開包含以上代碼的頁面之後,又單擊“後退”按鈕返回該頁面,就會看到 showCount 每次遞增的值。這是因爲該變量的狀態,乃至整個頁面的狀態,都被保存在了內存中,當你返回這個頁 面時,它們的狀態得到了恢復。如果你單擊了瀏覽器的“刷新”按鈕,那麼 showCount 的值就會被重 置爲 0,因爲頁面已經完全重新加載了。 除了通常的屬性之外,pageshow 事件的 event 對象還包含一個名爲 persisted 的布爾值屬性。 如果頁面被保存在了 bfcache中,則這個屬性的值爲 true;否則,這個屬性的值爲 false。可以像下面 這樣在事件處理程序中檢測這個屬性。 
 

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

通過檢測 persisted 屬性,就可以根據頁面在 bfcache中的狀態來確定是否需要採取其他操作。 與 pageshow 事件對應的是 pagehide 事件,該事件會在瀏覽器卸載頁面的時候觸發,而且是在 unload 事件之前觸發。與 pageshow 事件一樣,pagehide 在 document 上面觸發,但其事件處理程 序必須要添加到 window 對象。這個事件的 event 對象也包含 persisted 屬性,不過其用途稍有不同。 來看下面的例子。 
 

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

有時候,可能需要在 pagehide 事件觸發時根據 persisted 的值採取不同的操作。對於 pageshow 事件,如果頁面是從 bfcache中加載的,那麼 persisted 的值就是 true;對於 pagehide 事件,如果 頁面在卸載之後會被保存在 bfcache中,那麼 persisted 的值也會被設置爲 true。因此,當第一次觸 發 pageshow 時,persisted 的值一定是 false,而在第一次觸發 pagehide 時,persisted 就會變 成 true(除非頁面不會被保存在 bfcache中)。 支持 pageshow 和 pagehide 事件的瀏覽器有 Firefox、Safari 5+、Chrome和 Opera。IE9及之前版 本不支持這兩個事件。 

指定了 onunload 事件處理程序的頁面會被自動排除在 bfcache之外,即使事件 處理程序是空的。原因在於,onunload 常用於撤銷在 onload 中所執行的操作, 而跳過 onload 後再次顯示頁面很可能就會導致頁面不正常。 


6. hashchange 事件 

HTML5新增了 hashchange 事件,以便在 URL的參數列表(及 URL中“#”號後面的所有字符串) 發生變化時通知開發人員。之所以新增這個事件,是因爲在 Ajax應用中,開發人員經常要利用 URL參 數列表來保存狀態或導航信息。 必須要把 hashchange 事件處理程序添加給 window 對象,然後 URL參數列表只要變化就會調用 它。此時的 event 對象應該額外包含兩個屬性:oldURL 和 newURL。這兩個屬性分別保存着參數列表 變化前後的完整 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 對象來確定當前的參數列表。 

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

使用以下代碼可以檢測瀏覽器是否支持 hashchange 事件

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

 如果 IE8 是在 IE7 文檔模式下運行,即使功能無效它也會返回 true。爲解決這個問題,可以使用 以下這個更穩妥的檢測方式: 

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

設備事件

智能手機和平板電腦的普及,爲用戶與瀏覽器交互引入了一種新的方式,而一類新事件也應運而生。 設備事件(device event)可以讓開發人員確定用戶在怎樣使用設備。W3C從 2011年開始着手製定一份 關於設備事件的新草案(http://dev.w3.org/geo/api/spec-source-orientation.html),以涵蓋不斷增長的設備 類型併爲它們定義相關的事件。本節會同時討論這份草案中涉及的 API和特定於瀏覽器開發商的事件。

1. orientationchange 事件 

蘋果公司爲移動 Safari中添加了 orientationchange 事件,以便開發人員能夠確定用戶何時將設 備由橫向查看模式切換爲縱向查看模式。移動 Safari的 window.orientation 屬性中可能包含 3個值: 0 表示肖像模式,90 表示向左旋轉的橫向模式(“主屏幕”按鈕在右側),-90 表示向右旋轉的橫向模 式(“主屏幕”按鈕在左側)。相關文檔中還提到一個值,即 180 表示 iPhone頭朝下;但這種模式至今 尚未得到支持。圖 13-10展示了 window.orientation 的每個值的含義。 

只要用戶改變了設備的查看模式,就會觸發 orientationchange 事件。此時的 event 對象不包 含任何有價值的信息,因爲唯一相關的信息可以通過 window.orientation 訪問到。下面是使用這個 事件的典型示例。 
 


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

在這個例子中,當觸發 load 事件時會顯示初的方向信息。然後,添加了處理 orientationchange 事件的處理程序。只要發生這個事件,就會有表示新方向的信息更新頁面中的消息。 所有 iOS設備都支持 orientationchange 事件和 window.orientation 屬性。 
 

由於可以將 orientationchange 看成 window 事件,所以也可以通過指定 <body>元素的 onorientationchange 特性來指定事件處理程序。 

2. MozOrientation 事件 

Firefox 3.6爲檢測設備的方向引入了一個名爲 MozOrientation 的新事件。(前綴 Moz 表示這是特 定於瀏覽器開發商的事件,不是標準事件。)當設備的加速計檢測到設備方向改變時,就會觸發這個事 件。但這個事件與 iOS中的 orientationchange 事件不同,該事件只能提供一個平面的方向變化。由 於 MozOrientation 事件是在 window 對象上觸發的,所以可以使用以下代碼來處理。 

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

此時的 event 對象包含三個屬性:x、y 和 z。這幾個屬性的值都介於 1到-1之間,表示不同座標 軸上的方向。在靜止狀態下,x 值爲 0,y 值爲 0,z 值爲 1(表示設備處於豎直狀態)。如果設備向右傾 斜,x 值會減小;反之,向左傾斜,x 值會增大。類似地,如果設備向遠離用戶的方向傾斜,y 值會減 小,向接近用戶的方向傾斜,y 值會增大。z 軸檢測垂直加速度度,1 表示靜止不動,在設備移動時值 會減小。(失重狀態下值爲 0。)以下是輸出這三個值的一個簡單的例子。 

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

只有帶加速計的設備才支持 MozOrientation 事件,包括 Macbook、Lenovo Thinkpad、Windows Mobile和 Android設備。請大家注意,這是一個實驗性 API,將來可能會變(可能會被其他事件取代)。
 3. deviceorientation 事件 

本質上,DeviceOrientation Event規範定義的 deviceorientation 事件與 MozOrientation 事件類 似。它也是在加速計檢測到設備方向變化時在 window 對象上觸發,而且具有與 MozOrientation 事件 相同的支持限制。不過,deviceorientation 事件的意圖是告訴開發人員設備在空間中朝向哪兒,而 不是如何移動。 

設備在三維空間中是靠 x、y和 z軸來定位的。當設備靜止放在水平表面上時,這三個值都是 0。x 軸方向是從左往右,y軸方向是從下往上,z軸方向是從後往前(參見圖 13-11)。 

觸發 deviceorientation 事件時,事件對象中包含着每個軸相對於設備靜止狀態下發生變化的信 息。事件對象包含以下 5個屬性。 

  •  alpha:在圍繞 z軸旋轉時(即左右旋轉時),y軸的度數差;是一個介於0到 360之間的浮點數。
  •   beta:在圍繞 x軸旋轉時(即前後旋轉時), z軸的度數差;是一個介於180到180之間的浮點數。
  •   gamma:在圍繞y軸旋轉時(即扭轉設備時),z軸的度數差;是一個介於90到90之間的浮點數。
  •   absolute:布爾值,表示設備是否返回一個絕對值。
  •  compassCalibrated:布爾值,表示設備的指南針是否校準過。 

圖 13-12是 alpha、beta 和 gamma 值含義的示意圖。 

下面是一個輸出 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>";
 }); 

通過這些信息,可以響應設備的方向,重新排列或修改屏幕上的元素。要響應設備方向的改變而旋 轉元素,可以參考如下代碼。 

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

 

這個例子只能在移動 WebKit 瀏覽器中運行,因爲它使用了專有的 webkitTransform 屬性(即 CSS 標準屬性 transform 的臨時版)。元素“arrow”會隨着 event.alpha 值的變化而旋轉,給人一種指南 針的感覺。爲了保證旋轉平滑,這裏的 CSS3變換使用了舍入之後的值。 

到 2011年,支持 deviceorientation 事件的瀏覽器有 iOS 4.2+中的 Safari、Chrome和 Android版 WebKit。 

4. devicemotion 事件 

DeviceOrientation Event 規範還定義了一個 devicemotion 事件。這個事件是要告訴開發人員設備 什麼時候移動,而不僅僅是設備方向如何改變。例如,通過 devicemotion 能夠檢測到設備是不是正在 往下掉,或者是不是被走着的人拿在手裏。 

觸發 devicemotion 事件時,事件對象包含以下屬性。 

  •  acceleration:一個包含 x、y 和 z 屬性的對象,在不考慮重力的情況下,告訴你在每個方向 上的加速度。
  •  accelerationIncludingGravity:一個包含 x、y 和 z 屬性的對象,在考慮 z 軸自然重力加 速度的情況下,告訴你在每個方向上的加速度。 
  • interval:以毫秒錶示的時間值,必須在另一個 devicemotion 事件觸發前傳入。這個值在每 個事件中應該是一個常量。
  •  rotationRate:一個包含表示方向的 alpha、beta 和 gamma 屬性的對象。  

如果讀取不到 acceleration、accelerationIncludingGravity 和 rotationRate 值,則它們 的值爲 null。因此,在使用這三個屬性之前,應該先檢測確定它們的值不是 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 實現了 devicemotion 事件。 

 

觸摸與手勢事件

iOS版 Safari爲了向開發人員傳達一些特殊信息,新增了一些專有事件。因爲 iOS設備既沒有鼠標 也沒有鍵盤,所以在爲移動 Safari開發交互性網頁時,常規的鼠標和鍵盤事件根本不夠用。隨着 Android 中的 WebKit 的加入,很多這樣的專有事件變成了事實標準,導致 W3C開始制定 Touch Events規範(參 見 https://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html)。以下介紹的事件只針對觸摸設備。 

1. 觸摸事件 

包含 iOS 2.0軟件的 iPhone 3G發佈時,也包含了一個新版本的 Safari瀏覽器。這款新的移動 Safari 提供了一些與觸摸(touch)操作相關的新事件。後來,Android上的瀏覽器也實現了相同的事件。觸摸 事件會在用戶手指放在屏幕上面時、在屏幕上滑動時或從屏幕上移開時觸發。具體來說,有以下幾個觸 摸事件。 

  •  touchstart:當手指觸摸屏幕時觸發;即使已經有一個手指放在了屏幕上也會觸發。
  •  touchmove:當手指在屏幕上滑動時連續地觸發。在這個事件發生期間,調用preventDefault() 可以阻止滾動。
  •  touchend:當手指從屏幕上移開時觸發。
  •  touchcancel:當系統停止跟蹤觸摸時觸發。關於此事件的確切觸發時間,文檔中沒有明確說明。

 上面這幾個事件都會冒泡,也都可以取消。雖然這些觸摸事件沒有在 DOM規範中定義,但它們卻 是以兼容 DOM的方式實現的。因此,每個觸摸事件的 event 對象都提供了在鼠標事件中常見的屬性: bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、 ctrlKey 和 metaKey。

除了常見的 DOM屬性外,觸摸事件還包含下列三個用於跟蹤觸摸的屬性。

  •  touches:表示當前跟蹤的觸摸操作的 Touch 對象的數組。
  •  targetTouchs:特定於事件目標的 Touch 對象的數組。
  •  changeTouches:表示自上次觸摸以來發生了什麼改變的 Touch 對象的數組。

每個 Touch 對象包含下列屬性。 

  •  clientX:觸摸目標在視口中的 x座標。
  •  clientY:觸摸目標在視口中的 y座標。
  •  identifier:標識觸摸的唯一 ID。
  •  pageX:觸摸目標在頁面中的 x座標。
  •  pageY:觸摸目標在頁面中的 y座標。
  •  screenX:觸摸目標在屏幕中的 x座標。
  •  screenY:觸摸目標在屏幕中的 y座標。
  •  target:觸摸的 DOM節點目標。

使用這些屬性可以跟蹤用戶對屏幕的觸摸操作。來看下面的例子。 

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();  //阻止滾動
                 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); 

以上代碼會跟蹤屏幕上發生的一次觸摸操作。爲簡單起見,只會在有一次活動觸摸操作的情況下輸 出信息。當 touchstart 事件發生時,會將觸摸的位置信息輸出到<div>元素中。當 touchmove 事件 發生時,會取消其默認行爲,阻止滾動(觸摸移動的默認行爲是滾動頁面),然後輸出觸摸操作的變化 信息。而 touchend 事件則會輸出有關觸摸操作的終信息。注意,在 touchend 事件發生時,touches 集合中就沒有任何 Touch 對象了,因爲不存在活動的觸摸操作;此時,就必須轉而使用 changeTouchs 集合。 

這些事件會在文檔的所有元素上面觸發,因而可以分別操作頁面的不同部分。在觸摸屏幕上的元素 時,這些事件(包括鼠標事件)發生的順序如下: 

  • (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支持多點觸摸。 桌面版 Firefox 6+和 Chrome也支持觸摸事件。 

2. 手勢事件 

iOS 2.0中的 Safari還引入了一組手勢事件。當兩個手指觸摸屏幕時就會產生手勢,手勢通常會改變 顯示項的大小,或者旋轉顯示項。有三個手勢事件,分別介紹如下。 

  • gesturestart:當一個手指已經按在屏幕上而另一個手指又觸摸屏幕時觸發。
  •  gesturechange:當觸摸屏幕的任何一個手指的位置發生變化時觸發。
  •  gestureend:當任何一個手指從屏幕上面移開時觸發。 

只有兩個手指都觸摸到事件的接收容器時纔會觸發這些事件。在一個元素上設置事件處理程序,意 味着兩個手指必須同時位於該元素的範圍之內,才能觸發手勢事件(這個元素就是目標)。由於這些事 件冒泡,所以將事件處理程序放在文檔上也可以處理所有手勢事件。此時,事件的目標就是兩個手指都 位於其範圍內的那個元素。 

觸摸事件和手勢事件之間存在某種關係。當一個手指放在屏幕上時,會觸發 touchstart 事件。如 果另一個手指又放在了屏幕上,則會先觸發 gesturestart 事件,隨後觸發基於該手指的 touchstart 事件。如果一個或兩個手指在屏幕上滑動,將會觸發 gesturechange 事件。但只要有一個手指移開, 就會觸發 gestureend 事件,緊接着又會觸發基於該手指的 touchend 事件。 

與觸摸事件一樣,每個手勢事件的 event 對象都包含着標準的鼠標事件屬性:bubbles、 cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、 ctrlKey 和 metaKey。此外,還包含兩個額外的屬性:rotation 和 scale。其中,rotation 屬性表 示手指變化引起的旋轉角度,負值表示逆時針旋轉,正值表示順時針旋轉(該值從 0開始)。而 scale 屬性表示兩個手指間距離的變化情況(例如向內收縮會縮短距離);這個值從 1 開始,並隨距離拉大而 增長,隨距離縮短而減小。 

下面是使用手勢事件的一個示例。 

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

與前面演示觸摸事件的例子一樣,這裏的代碼只是將每個事件都關聯到同一個函數中,然後通過該 函數輸出每個事件的相關信息。 

觸摸事件也會返回 rotation 和 scale 屬性,但這兩個屬性只會在兩個手指與 屏幕保持接觸時纔會發生變化。一般來說,使用基於兩個手指的手勢事件,要比管理 觸摸事件中的所有交互要容易得多。 

 

內存和性能

由於事件處理程序可以爲現代 Web 應用程序提供交互能力,因此許多開發人員會不分青紅皁白地 向頁面中添加大量的處理程序。在創建 GUI的語言(如 C#)中,爲 GUI中的每個按鈕添加一個 onclick 事件處理程序是司空見慣的事,而且這樣做也不會導致什麼問題。可是在 JavaScript中,添加到頁面上 的事件處理程序數量將直接關係到頁面的整體運行性能。導致這一問題的原因是多方面的。首先,每個 函數都是對象,都會佔用內存;內存中的對象越多,性能就越差。其次,必須事先指定所有事件處理程 序而導致的 DOM訪問次數,會延遲整個頁面的交互就緒時間。事實上,從如何利用好事件處理程序的 角度出發,還是有一些方法能夠提升性能的。 

事件委託

對“事件處理程序過多”問題的解決方案就是事件委託。事件委託利用了事件冒泡,只指定一個事 件處理程序,就可以管理某一類型的所有事件。例如,click 事件會一直冒泡到 document 層次。也就 是說,我們可以爲整個頁面指定一個 onclick 事件處理程序,而不必給每個可單擊的元素分別添加事 件處理程序。以下面的 HTML代碼爲例。 

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

其中包含 3個被單擊後會執行操作的列表項。按照傳統的做法,需要像下面這樣爲它們添加 3個事 件處理程序。 

var item1 = document.getElementById("goSomewhere");
 var item2 = document.getElementById("doSomething");
 var item3 = document.getElementById("sayHi"); 
 
EventUtil.addHandler(item1, "click", function(event){ 
    location.href = "http://www.wrox.com"; }); 
 
EventUtil.addHandler(item2, "click", function(event){ 
    document.title = "I changed the document's title";
 }); 
 
EventUtil.addHandler(item3, "click", function(event){
     alert("hi");
 }); 
 

如果在一個複雜的 Web 應用程序中,對所有可單擊的元素都採用這種方式,那麼結果就會有數不 清的代碼用於添加事件處理程序。此時,可以利用事件委託技術解決這個問題。使用事件委託,只需在 DOM樹中儘量高的層次上添加一個事件處理程序,如下面的例子所示。 

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.href = "http://www.wrox.com";
             break; 
 
        case "sayHi":
             alert("hi");
             break;
     }
 }); 
 

在這段代碼裏,我們使用事件委託只爲<ul>元素添加了一個 onclick 事件處理程序。由於所有列 表項都是這個元素的子節點,而且它們的事件會冒泡,所以單擊事件終會被這個函數處理。

我們知道, 事件目標是被單擊的列表項,故而可以通過檢測 id 屬性來決定採取適當的操作。

與前面未使用事件委 託的代碼比一比,會發現這段代碼的事前消耗更低,因爲只取得了一個 DOM元素,只添加了一個事件 處理程序。雖然對用戶來說終的結果相同,但這種技術需要佔用的內存更少。

所有用到按鈕的事件(多 數鼠標事件和鍵盤事件)都適合採用事件委託技術。 如果可行的話,也可以考慮爲 document 對象添加一個事件處理程序,用以處理頁面上發生的某種 特定類型的事件。這樣做與採取傳統的做法相比具有如下優點。 

  •  document 對象很快就可以訪問,而且可以在頁面生命週期的任何時點上爲它添加事件處理程序 (無需等待 DOMContentLoaded 或 load 事件)。換句話說,只要可單擊的元素呈現在頁面上, 就可以立即具備適當的功能。
  •  在頁面中設置事件處理程序所需的時間更少。只添加一個事件處理程序所需的 DOM引用更少, 所花的時間也更少。
  •  整個頁面佔用的內存空間更少,能夠提升整體性能。 

適合採用事件委託技術的事件包括click、mousedown、mouseup、keydown、keyup 和keypress。 雖然 mouseover 和mouseout 事件也冒泡,但要適當處理它們並不容易,而且經常需要計算元素的位置。 (因爲當鼠標從一個元素移到其子節點時,或者當鼠標移出該元素時,都會觸發 mouseout 事件。) 

移除事件程序

每當將事件處理程序指定給元素時,運行中的瀏覽器代碼與支持頁面交互的 JavaScript 代碼之間就 會建立一個連接。這種連接越多,頁面執行起來就越慢。如前所述,可以採用事件委託技術,限制建立 的連接數量。另外,在不需要的時候移除事件處理程序,也是解決這個問題的一種方案。內存中留有那 些過時不用的“空事件處理程序”(dangling event handler),也是造成 Web 應用程序內存與性能問題的 主要原因。 

在兩種情況下,可能會造成上述問題。第一種情況就是從文檔中移除帶有事件處理程序的元素時。 這可能是通過純粹的 DOM操作,例如使用 removeChild()和 replaceChild()方法,但更多地是發 生在使用 innerHTML 替換頁面中某一部分的時候。如果帶有事件處理程序的元素被 innerHTML 刪除 了,那麼原來添加到元素中的事件處理程序極有可能無法被當作垃圾回收。來看下面的例子。 

<div id="myDiv">
     <input type="button" value="Click Me" id="myBtn">
 </div>
 <script type="text/javascript">
     var btn = document.getElementById("myBtn");
     btn.onclick = function(){
 
 
        //先執行某些操作 
 
        document.getElementById("myDiv").innerHTML = "Processing...";
         //麻煩了!     
    }; 
</script> 
 

這裏,有一個按鈕被包含在<div>元素中。爲避免雙擊,單擊這個按鈕時就將按鈕移除並替換成一 條消息;這是網站設計中非常流行的一種做法。但問題在於,當按鈕被從頁面中移除時,它還帶着一個 事件處理程序呢。在<div>元素上設置 innerHTML 可以把按鈕移走,但事件處理程序仍然與按鈕保持 着引用關係。有的瀏覽器(尤其是 IE)在這種情況下不會作出恰當地處理,它們很有可能會將對元素和 對事件處理程序的引用都保存在內存中。如果你知道某個元素即將被移除,那麼好手工移除事件處理 程序,如下面的例子所示。 

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

在此,我們在設置<div>的 innerHTML 屬性之前,先移除了按鈕的事件處理程序。這樣就確保了 內存可以被再次利用,而從 DOM中移除按鈕也做到了乾淨利索。 注意,在事件處理程序中刪除按鈕也能阻止事件冒泡。目標元素在文檔中是事件冒泡的前提。 

採用事件委託也有助於解決這個問題。如果事先知道將來有可能使用innerHTML 替換掉頁面中的某一部分,那麼就可以不直接把事件處理程序添加到該部分的元素 中。而通過把事件處理程序指定給較高層次的元素,同樣能夠處理該區域中的事件。
 

導致“空事件處理程序”的另一種情況,就是卸載頁面的時候。毫不奇怪,IE8 及更早版本在這種 情況下依然是問題多的瀏覽器,儘管其他瀏覽器或多或少也有類似的問題。如果在頁面被卸載之前沒 有清理乾淨事件處理程序,那它們就會滯留在內存中。每次加載完頁面再卸載頁面時(可能是在兩個頁 面間來回切換,也可以是單擊了“刷新”按鈕),內存中滯留的對象數目就會增加,因爲事件處理程序 佔用的內存並沒有被釋放。 

一般來說,好的做法是在頁面卸載之前,先通過 onunload 事件處理程序移除所有事件處理程序。 在此,事件委託技術再次表現出它的優勢——需要跟蹤的事件處理程序越少,移除它們就越容易。對這 種類似撤銷的操作,我們可以把它想象成:只要是通過 onload 事件處理程序添加的東西,後都要通 過 onunload 事件處理程序將它們移除。

不要忘了,使用 onunload 事件處理程序意味着頁面不會被緩存在 bfcache中。 如果你在意這個問題,那麼就只能在IE中通過 onunload 來移除事件處理程序了

 

 

模擬事件

什麼是模擬事件?

事件,就是網頁中某個特別值得關注的瞬間。事件經常由用戶操作或通過其他瀏覽器功能來觸發。 但很少有人知道,也可以使用 JavaScript 在任意時刻來觸發特定的事件,而此時的事件就如同瀏覽器創 建的事件一樣。也就是說,這些事件該冒泡還會冒泡,而且照樣能夠導致瀏覽器執行已經指定的處理它 們的事件處理程序。在測試 Web 應用程序,模擬觸發事件是一種極其有用的技術。DOM2 級規範爲此 規定了模擬特定事件的方式,IE9、Opera、Firefox、Chrome和 Safari都支持這種方式。IE有它自己模擬 事件的方式。

DOM中的事件模擬

可以在 document 對象上使用 createEvent()方法創建 event 對象。這個方法接收一個參數,即 表示要創建的事件類型的字符串。在 DOM2 級中,所有這些字符串都使用英文複數形式,而在 DOM3級中都變成了單數。這個字符串可以是下列幾字符串之一。 

  • UIEvents:一般化的 UI事件。鼠標事件和鍵盤事件都繼承自 UI事件。DOM3級中是 UIEvent。
  • MouseEvents:一般化的鼠標事件。DOM3級中是 MouseEvent。 
  • MutationEvents:一般化的 DOM變動事件。DOM3級中是 MutationEvent。 
  • HTMLEvents:一般化的 HTML事件。沒有對應的 DOM3級事件(HTML事件被分散到其他類 別中)。 

要注意的是,“DOM2 級事件”並沒有專門規定鍵盤事件,後來的“DOM3 級事件”中才正式將其 作爲一種事件給出規定。IE9是目前唯一支持 DOM3級鍵盤事件的瀏覽器。不過,在其他瀏覽器中,在 現有方法的基礎上,可以通過幾種方式來模擬鍵盤事件。 

在創建了 event 對象之後,還需要使用與事件有關的信息對其進行初始化。每種類型的 event 對 象都有一個特殊的方法,爲它傳入適當的數據就可以初始化該 event 對象。不同類型的這個方法的名 字也不相同,具體要取決於 createEvent()中使用的參數。 

模擬事件的後一步就是觸發事件。這一步需要使用 dispatchEvent()方法,所有支持事件的 DOM 節點都支持這個方法。調用 dispatchEvent()方法時,需要傳入一個參數,即表示要觸發事件 的 event 對象。觸發事件之後,該事件就躋身“官方事件”之列了,因而能夠照樣冒泡並引發相應事 件處理程序的執行。 

1. 模擬鼠標事件

2. 模擬鍵盤事件 

3. 模擬其他事件 

4. 自定義 DOM事件

IE中的事件模擬

在 IE8及之前版本中模擬事件與在 DOM中模擬事件的思路相似:先創建 event 對象,然後爲其指 定相應的信息,然後再使用該對象來觸發事件。當然,IE在實現每個步驟時都採用了不一樣的方式。

調用 document.createEventObject()方法可以在 IE中創建 event 對象。但與 DOM方式不同 的是,這個方法不接受參數,結果會返回一個通用的 event 對象。然後,你必須手工爲這個對象添加 所有必要的信息(沒有方法來輔助完成這一步驟)。後一步就是在目標上調用 fireEvent()方法,這 個方法接受兩個參數:事件處理程序的名稱和 event 對象。在調用 fireEvent()方法時,會自動爲 event 對象添加 srcElement 和 type 屬性;其他屬性則都是必須通過手工添加的。換句話說,模擬任 何IE支持的事件都採用相同的模式。例如,下面的代碼模擬了在一個按鈕上觸發 click 事件過程。 

var btn = document.getElementById("myBtn"); 
 
//創建事件對象 
var event = document.createEventObject(); 
 
//初始化事件對象 event.screenX = 100; 
event.screenY = 0; 
event.clientX = 0; 
event.clientY = 0; 
event.ctrlKey = false; 
event.altKey = false;
event.shiftKey = false; event.button = 0; 
 
//觸發事件 
btn.fireEvent("onclick", event); 

這個例子先創建了一個 event 對象,然後又用一些信息對其進行了初始化。注意,這裏可以爲對 象隨意添加屬性,不會有任何限制——即使添加的屬性 IE8及更早版本並不支持也無所謂。在此添加的 屬性對事件沒有什麼影響,因爲只有事件處理程序纔會用到它們。 採用相同的模式也可以模擬觸發 keypress 事件,如下面的例子所示。 

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

 
//創建事件對象 
var event = document.createEventObject(); 
 
//初始化事件對象 event.altKey = false;
 event.ctrlKey = false; 
event.shiftKey = false;
 event.keyCode = 65; 
 
//觸發事件 
textbox.fireEvent("onkeypress", event); 

由於鼠標事件、鍵盤事件以及其他事件的 event 對象並沒有什麼不同,所以可以使用通用對象來 觸發任何類型的事件。不過,正如在DOM中模擬鍵盤事件一樣,運行這個例子也不會因模擬了keypress 而在文本框中看到任何字符,即使觸發了事件處理程序也沒有用。 

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