国产AV88|国产乱妇无码在线观看|国产影院精品在线观看十分钟福利|免费看橹橹网站

javascript-gaojichengx有目錄u

發(fā)布時(shí)間:2024-12-25 | 雜志分類:其他
免費(fèi)制作
更多內(nèi)容

javascript-gaojichengx有目錄u

{{`發(fā)布時(shí)間:2024-12-25`}} | 云展網(wǎng)畫冊制作 宣傳冊 其他 javascript-gaojichengx有目錄u
13.4 事件類型 383 14 2 3 1718 13 197 8 9 1011 12var textbox = document.getElementById(\"myText\"); EventUtil.addHandler(textbox, \"keypress\", function(event){ event = EventUtil.getEvent(event); if (event.getModifierState){ alert(event.getModifierState(\"Shift\")); } }); DOMLevel3LocationGetModifierStateExample01.htm 實(shí)際上,通過 event 對象的 shiftKey、altKey、ctrlKey 和 metaKey 屬性已經(jīng)可以取得類似的屬性了。IE9 是唯一支持 getModifierState()方法的瀏覽器。4. textInput 事件“DOM3 級(jí)事件”規(guī)... [收起]
[展開]
javascript-gaojichengx有目錄u
粉絲: {{bookData.followerCount}}
文本內(nèi)容
第401頁

13.4 事件類型 383

14

2

3

17

18

13

19

7

8

9

10

11

12

var textbox = document.getElementById(\"myText\");

EventUtil.addHandler(textbox, \"keypress\", function(event){

event = EventUtil.getEvent(event);

if (event.getModifierState){

alert(event.getModifierState(\"Shift\"));

}

});

DOMLevel3LocationGetModifierStateExample01.htm

實(shí)際上,通過 event 對象的 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 對象中還包含一個(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);

});

TextInputEventExample01.htm

在這個(gè)例子中,插入到文本框中的字符會(huì)通過一個(gè)警告框顯示出來。

另外,event 對象上還有一個(gè)屬性,叫 inputMethod,表示把文本輸入到文本框中的方式。

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

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

? 2,表示文本是粘貼進(jìn)來的。

? 3,表示文本是拖放進(jìn)來的。

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

? 5,表示文本是通過在表單中選擇某一項(xiàng)輸入的。

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

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

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

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

使用這個(gè)屬性可以確定文本是如何輸入到控件中的,從而可以驗(yàn)證其有效性。支持 textInput 屬

性的瀏覽器有 IE9+、Safari 和 Chrome。只有 IE 支持 inputMethod 屬性。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第402頁

384 第 13 章 事件

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

任天堂 Wii 會(huì)在用戶按下 Wii 遙控器上的按鍵時(shí)觸發(fā)鍵盤事件。盡管沒有辦法訪問 Wii 遙控器中的

所有按鍵,但還是有一些鍵可以觸發(fā)鍵盤事件。圖 13-6 展示了一些鍵的鍵碼,通過這些鍵碼可以知道

用戶按下了哪個(gè)鍵。

圖 13-8

當(dāng)用戶按下十字鍵盤(鍵碼為 175~178)、減號(hào)(170)、加號(hào)(174)、1(172)或 2(173)鍵時(shí)就

會(huì)觸發(fā)鍵盤事件。但沒有辦法得知用戶是否按下了電源開關(guān)、A、B 或主頁鍵。

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

13.4.5 復(fù)合事件

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

IME(Input Method Editor,輸入法編輯器)可以讓用戶輸入在物理鍵盤上找不到的字符。例如,使用拉

丁文鍵盤的用戶通過 IME 照樣能輸入日文字符。IME 通常需要同時(shí)按住多個(gè)鍵,但最終只輸入一個(gè)字

符。復(fù)合事件就是針對檢測和處理這種輸入而設(shè)計(jì)的。有以下三種復(fù)合事件。

? compositionstart:在 IME 的文本復(fù)合系統(tǒng)打開時(shí)觸發(fā),表示要開始輸入了。

(無法訪問)

(無法訪問)

(無法訪問)

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第403頁

13.4 事件類型 385

14

2

3

17

18

13

19

7

8

9

10

11

12

? compositionupdate:在向輸入字段中插入新字符時(shí)觸發(fā)。

? compositionend:在 IME 的文本復(fù)合系統(tǒng)關(guān)閉時(shí)觸發(fā),表示返回正常鍵盤輸入狀態(tài)。

復(fù)合事件與文本事件在很多方面都很相似。在觸發(fā)復(fù)合事件時(shí),目標(biāo)是接收文本的輸入字段。但它

比文本事件的事件對象多一個(gè)屬性 data,其中包含以下幾個(gè)值中的一個(gè):

? 如果在 compositionstart 事件發(fā)生時(shí)訪問,包含正在編輯的文本(例如,已經(jīng)選中的需要馬

上替換的文本);

? 如果在 compositionupdate 事件發(fā)生時(shí)訪問,包含正插入的新字符;

? 如果在 compositionend 事件發(fā)生時(shí)訪問,包含此次輸入會(huì)話中插入的所有字符。

與文本事件一樣,必要時(shí)可以利用復(fù)合事件來篩選輸入??梢韵裣旅孢@樣使用它們:

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

});

CompositionEventsExample01.htm

IE9+是到 2011 年唯一支持復(fù)合事件的瀏覽器。由于缺少支持,對于需要開發(fā)跨瀏覽器應(yīng)用的開發(fā)

人員,它的用處不大。要確定瀏覽器是否支持復(fù)合事件,可以使用以下代碼:

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

13.4.6 變動(dòng)事件

DOM2 級(jí)的變動(dòng)(mutation)事件能在 DOM 中的某一部分發(fā)生變化時(shí)給出提示。變動(dòng)事件是為 XML

或 HTML DOM 設(shè)計(jì)的,并不特定于某種語言。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ā)。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第404頁

386 第 13 章 事件

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

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

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

事 件 Opera 9+ Firefox 3+ Safari 3+及Chrome IE9+

DOMSubtreeModified - 支持 支持 支持

DOMNodeInserted 支持 支持 支持 支持

DOMNodeRemoved 支持 支持 支持 支持

由于 DOM3 級(jí)事件模塊作廢了很多變動(dòng)事件,所以本節(jié)只介紹那些將來仍然會(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 屬性中包含著對

目標(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

對象中不包含其他信息。

緊隨其后觸發(fā)的是 DOMSubtreeModified 事件。這個(gè)事件的目標(biāo)是被移除節(jié)點(diǎn)的父節(jié)點(diǎn);此時(shí)的

event 對象也不會(huì)提供與事件相關(guān)的其他信息。

為了理解上述事件的觸發(fā)過程,下面我們就以一個(gè)簡單的 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>

在這個(gè)例子中,我們假設(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

的直接子元素。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第405頁

13.4 事件類型 387

14

2

3

17

18

13

19

7

8

9

10

11

12

運(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 添加了針對 DOMSubtreeModified 和 DOMNodeRemoved 事件的處理程序,

以便在頁面上處理這些事件。由于 DOMNodeRemovedFromDocument 不會(huì)冒泡,所以我們將針對它的

事件處理程序直接添加給了<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è)對父節(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 對象中不包含其他信息。

最后一個(gè)觸發(fā)的事件是 DOMSubtreeModified,觸發(fā)于新插入節(jié)點(diǎn)的父節(jié)點(diǎn)。

我們?nèi)砸郧懊娴?HTML 文檔為例,可以通過下列 JavaScript 代碼來驗(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);

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第406頁

388 第 13 章 事件

alert(event.target);

});

list.appendChild(item);

});

以上代碼首先創(chuàng)建了一個(gè)包含文本\"Item 4\"的新<li>元素。由于 DOMSubtreeModified 和

DOMNodeInserted 事件是冒泡的,所以把它們的事件處理程序添加到了文檔中。在將列表項(xiàng)插入到其

父節(jié)點(diǎn)之前,先將 DOMNodeInsertedIntoDocument 事件的事件處理程序添加給它。最后一步就是使

用 appendChild()來添加這個(gè)列表項(xiàng);此時(shí),事件開始依次被觸發(fā)。首先是在新<li>元素項(xiàng)上觸發(fā)

DOMNodeInserted 事件,其 relatedNode 是<ul>元素。然后是觸發(fā)新<li>元素上的 DOMNodeInsertedIntoDocument 事件,最后觸發(fā)的是<ul>元素上的 DOMSubtreeModified 事件。

13.4.7 HTML5 事件

DOM 規(guī)范沒有涵蓋所有瀏覽器支持的所有事件。很多瀏覽器出于不同的目的——滿足用戶需求或

解決特殊問題,還實(shí)現(xiàn)了一些自定義的事件。HTML5 詳盡列出了瀏覽器應(yīng)該支持的所有事件。本節(jié)只

討論其中得到瀏覽器完善支持的事件,但并非全部事件。(其他事件會(huì)在本書其他章節(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è)事件處理程序,用以處理頁面

中發(fā)生的所有此類事件。這個(gè)事件的目標(biāo)是發(fā)生用戶操作的元素。在所有瀏覽器中都可以取消這個(gè)事件:

在兼容 DOM 的瀏覽器中,使用 event.preventDefalut();在 IE 中,將 event.returnValue 的值

設(shè)置為 false。因?yàn)?contextmenu 事件屬于鼠標(biāo)事件,所以其事件對象中包含與光標(biāo)位置有關(guān)的所有

屬性。通常使用 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>

ContextMenuEventExample01.htm

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第407頁

13.4 事件類型 389

14

2

3

17

18

13

19

7

8

9

10

11

12

這里的<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\";

});

});

ContextMenuEventExample01.htm

在這個(gè)例子中,我們?yōu)?lt;div>元素添加了 oncontextmenu 事件的處理程序。這個(gè)事件處理程序首

先會(huì)取消默認(rèn)行為,以保證不顯示瀏覽器默認(rèn)的上下文菜單。然后,再根據(jù) event 對象 clientX 和

clientY 屬性的值,來確定放置<ul>元素的位置。最后一步就是通過將 visibility 屬性設(shè)置為

\"visible\"來顯示自定義上下文菜單。另外,還為 document 添加了一個(gè) onclick 事件處理程序,以

便用戶能夠通過鼠標(biāo)單擊來隱藏菜單(單擊也是隱藏系統(tǒng)上下文菜單的默認(rèn)操作)。

雖然這個(gè)例子很簡單,但它卻展示了 Web 上所有自定義上下文菜單的基本結(jié)構(gòu)。只需為這個(gè)例子

中的上下文菜單添加一些 CSS 樣式,就可以得到非常棒的效果。

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

2. beforeunload 事件

之所以有發(fā)生在 window 對象上的 beforeunload 事件,是為了讓開發(fā)人員有可能在頁面卸載前

阻止這一操作。這個(gè)事件會(huì)在瀏覽器卸載頁面之前觸發(fā),可以通過它來取消卸載并繼續(xù)使用原有頁面。

但是,不能徹底取消這個(gè)事件,因?yàn)槟蔷拖喈?dāng)于讓用戶無法離開當(dāng)前頁面了。為此,這個(gè)事件的意圖是

將控制權(quán)交給用戶。顯示的消息會(huì)告知用戶頁面行將被卸載(正因?yàn)槿绱瞬艜?huì)顯示這個(gè)消息),詢問用

戶是否真的要關(guān)閉頁面,還是希望繼續(xù)留下來(見圖 13-9)。

圖 13-9

為了顯示這個(gè)彈出對話框,必須將 event.returnValue 的值設(shè)置為要顯示給用戶的字符串(對

IE 及 Fiefox 而言),同時(shí)作為函數(shù)的值返回(對 Safari 和 Chrome 而言),如下面的例子所示。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第408頁

390 第 13 章 事件

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;

});

BeforeUnloadEventExample01.htm

IE 和 Firefox、Safari 和 Chrome 都支持 beforeunload 事件,也都會(huì)彈出這個(gè)對話框詢問用戶是否

真想離開。Opera 11 及之前的版本不支持 beforeunload 事件。

3. DOMContentLoaded 事件

如前所述,window 的 load 事件會(huì)在頁面中的一切都加載完畢時(shí)觸發(fā),但這個(gè)過程可能會(huì)因?yàn)橐?/p>

加載的外部資源過多而頗費(fèi)周折。而 DOMContentLoaded 事件則在形成完整的 DOM 樹之后就會(huì)觸發(fā),

不理會(huì)圖像、JavaScript 文件、CSS 文件或其他資源是否已經(jīng)下載完畢。與 load 事件不同,

DOMContentLoaded 支持在頁面下載的早期添加事件處理程序,這也就意味著用戶能夠盡早地與頁面

進(jìn)行交互。

要處理 DOMContentLoaded 事件,可以為 document 或 window 添加相應(yīng)的事件處理程序(盡管

這個(gè)事件會(huì)冒泡到 window,但它的目標(biāo)實(shí)際上是 document)。來看下面的例子。

EventUtil.addHandler(document, \"DOMContentLoaded\", function(event){

alert(\"Content loaded\");

});

DOMContentLoadedEventExample01.htm

DOMContentLoaded 事件對象不會(huì)提供任何額外的信息(其 target 屬性是 document)。

IE9+、Firefox、Chrome、Safari 3.1+和 Opera 9+都支持 DOMContentLoaded 事件,通常這個(gè)事件

既可以添加事件處理程序,也可以執(zhí)行其他 DOM 操作。這個(gè)事件始終都會(huì)在 load 事件之前觸發(fā)。

對于不支持 DOMContentLoaded 的瀏覽器,我們建議在頁面加載期間設(shè)置一個(gè)時(shí)間為 0 毫秒的超

時(shí)調(diào)用,如下面的例子所示。

setTimeout(function(){

//在此添加事件處理程序

}, 0);

這段代碼的實(shí)際意思就是:“在當(dāng)前 JavaScript 處理完成后立即運(yùn)行這個(gè)函數(shù)?!痹陧撁嫦螺d和構(gòu)建

期間,只有一個(gè) JavaScript 處理過程,因此超時(shí)調(diào)用會(huì)在該過程結(jié)束時(shí)立即觸發(fā)。至于這個(gè)時(shí)間與

DOMContentLoaded 被觸發(fā)的時(shí)間能否同步,主要還是取決于用戶使用的瀏覽器和頁面中的其他代碼。

為了確保這個(gè)方法有效,必須將其作為頁面中的第一個(gè)超時(shí)調(diào)用;即便如此,也還是無法保證在所有環(huán)

境中該超時(shí)調(diào)用一定會(huì)早于 load 事件被觸發(fā)。

4. readystatechange 事件

IE 為 DOM 文檔中的某些部分提供了 readystatechange 事件。這個(gè)事件的目的是提供與文檔或

元素的加載狀態(tài)有關(guān)的信息,但這個(gè)事件的行為有時(shí)候也很難預(yù)料。支持 readystatechange 事件的

每個(gè)對象都有一個(gè) readyState 屬性,可能包含下列 5 個(gè)值中的一個(gè)。

? uninitialized(未初始化):對象存在但尚未初始化。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第409頁

13.4 事件類型 391

14

2

3

17

18

13

19

7

8

9

10

11

12

? loading(正在加載):對象正在加載數(shù)據(jù)。

? loaded(加載完畢):對象加載數(shù)據(jù)完成。

? interactive(交互):可以操作對象了,但還沒有完全加載。

? complete(完成):對象已經(jīng)加載完畢。

這些狀態(tài)看起來很直觀,但并非所有對象都會(huì)經(jīng)歷 readyState 的這幾個(gè)階段。換句話說,如果某

個(gè)階段不適用某個(gè)對象,則該對象完全可能跳過該階段;并沒有規(guī)定哪個(gè)階段適用于哪個(gè)對象。顯然,

這意味著 readystatechange 事件經(jīng)常會(huì)少于 4 次,而 readyState 屬性的值也不總是連續(xù)的。

對于 document 而言,值為\"interactive\"的 readyState 會(huì)在與 DOMContentLoaded 大致相

同的時(shí)刻觸發(fā) readystatechange 事件。此時(shí),DOM 樹已經(jīng)加載完畢,可以安全地操作它了,因此就

會(huì)進(jìn)入交互(interactive)階段。但與此同時(shí),圖像及其他外部文件不一定可用。下面來看一段處理

readystatechange 事件的代碼。

EventUtil.addHandler(document, \"readystatechange\", function(event){

if (document.readyState == \"interactive\"){

alert(\"Content loaded\");

}

});

這個(gè)事件的 event 對象不會(huì)提供任何信息,也沒有目標(biāo)對象。

在與 load 事件一起使用時(shí),無法預(yù)測兩個(gè)事件觸發(fā)的先后順序。在包含較多或較大的外部資源的

頁面中,會(huì)在 load 事件觸發(fā)之前先進(jìn)入交互階段;而在包含較少或較小的外部資源的頁面中,則很難

說 readystatechange 事件會(huì)發(fā)生在 load 事件前面。

讓問題變得更復(fù)雜的是,交互階段可能會(huì)早于也可能會(huì)晚于完成階段出現(xiàn),無法確保順序。在包含

較多外部資源的頁面中,交互階段更有可能早于完成階段出現(xiàn);而在頁面中包含較少外部資源的情況下,

完成階段先于交互階段出現(xiàn)的可能性更大。因此,為了盡可能搶到先機(jī),有必要同時(shí)檢測交互和完成階

段,如下面的例子所示。

EventUtil.addHandler(document, \"readystatechange\", function(event){

if (document.readyState == \"interactive\" || document.readyState == \"complete\"){

EventUtil.removeHandler(document, \"readystatechange\", arguments.callee);

alert(\"Content loaded\");

}

});

對于上面的代碼來說,當(dāng) readystatechange 事件觸發(fā)時(shí),會(huì)檢測 document.readyState 的值,

看當(dāng)前是否已經(jīng)進(jìn)入交互階段或完成階段。如果是,則移除相應(yīng)的事件處理程序以免在其他階段再執(zhí)行。

注意,由于事件處理程序使用的是匿名函數(shù),因此這里使用了 arguments.callee 來引用該函數(shù)。然

后,會(huì)顯示一個(gè)警告框,說明內(nèi)容已經(jīng)加載完畢。這樣編寫代碼可以達(dá)到與使用 DOMContentLoaded

十分相近的效果。

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

雖然使用 readystatechange 可以十分近似地模擬 DOMContentLoaded 事件,

但它們本質(zhì)上還是不同的。在不同頁面中,load 事件與 readystatechange 事件并

不能保證以相同的順序觸發(fā)。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第410頁

392 第 13 章 事件

另外, <script>(在 IE 和 Opera 中)和<link>(僅 IE 中)元素也會(huì)觸發(fā) readystatechange

事件,可以用來確定外部的 JavaScript 和 CSS 文件是否已經(jīng)加載完成。與在其他瀏覽器中一樣,除非把

動(dòng)態(tài)創(chuàng)建的元素添加到頁面中,否則瀏覽器不會(huì)開始下載外部資源?;谠赜|發(fā)的

readystatechange 事件也存在同樣的問題,即 readyState 屬性無論等于 \"loaded\" 還 是

\"complete\"都可以表示資源已經(jīng)可用。有時(shí)候,readyState 會(huì)停在\"loaded\"階段而永遠(yuǎn)不會(huì)“完成”;

有時(shí)候,又會(huì)跳過\"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);

});

ReadyStateChangeEventExample01.htm

這個(gè)例子為新創(chuàng)建的<script>節(jié)點(diǎn)指定了一個(gè)事件處理程序。事件的目標(biāo)是該節(jié)點(diǎn)本身,因此當(dāng)

觸 發(fā) readystatechange 事件時(shí),要檢測目標(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);

});

ReadyStateChangeEventExample02.htm

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第411頁

13.4 事件類型 393

14

2

3

17

18

13

19

7

8

9

10

11

12

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

5. pageshow 和 pagehide 事件

Firefox 和 Opera 有一個(gè)特性,名叫“往返緩存”(back-forward cache,或 bfcache),可以在用戶使

用瀏覽器的“后退”和“前進(jìn)”按鈕時(shí)加快頁面的轉(zhuǎn)換速度。這個(gè)緩存中不僅保存著頁面數(shù)據(jù),還保存

了 DOM 和 JavaScript 的狀態(tài);實(shí)際上是將整個(gè)頁面都保存在了內(nèi)存里。如果頁面位于 bfcache 中,那么

再次打開該頁面時(shí)就不會(huì)觸發(fā) load 事件。盡管由于內(nèi)存中保存了整個(gè)頁面的狀態(tài),不觸發(fā) load 事件

也不應(yīng)該會(huì)導(dǎo)致什么問題,但為了更形象地說明 bfcache 的行為,F(xiàn)irefox 還是提供了一些新事件。

第一個(gè)事件就是 pageshow,這個(gè)事件在頁面顯示時(shí)觸發(fā),無論該頁面是否來自 bfcache。在重新加

載的頁面中,pageshow 會(huì)在 load 事件觸發(fā)后觸發(fā);而對于 bfcache 中的頁面,pageshow 會(huì)在頁面狀

態(tài)完全恢復(fù)的那一刻觸發(fā)。另外要注意的是,雖然這個(gè)事件的目標(biāo)是 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.\");

});

})();

這個(gè)例子使用了私有作用域,以防止變量 showCount 進(jìn)入全局作用域。當(dāng)頁面首次加載完成時(shí),

showCount 的值為 0。此后,每當(dāng)觸發(fā) pageshow 事件,showCount 的值就會(huì)遞增并通過警告框顯示

出來。如果你在離開包含以上代碼的頁面之后,又單擊“后退”按鈕返回該頁面,就會(huì)看到 showCount

每次遞增的值。這是因?yàn)樵撟兞康臓顟B(tài),乃至整個(gè)頁面的狀態(tài),都被保存在了內(nèi)存中,當(dāng)你返回這個(gè)頁

面時(shí),它們的狀態(tài)得到了恢復(fù)。如果你單擊了瀏覽器的“刷新”按鈕,那么 showCount 的值就會(huì)被重

置為 0,因?yàn)轫撁嬉呀?jīng)完全重新加載了。

除了通常的屬性之外,pageshow 事件的 event 對象還包含一個(gè)名為 persisted 的布爾值屬性。

如果頁面被保存在了 bfcache 中,則這個(gè)屬性的值為 true;否則,這個(gè)屬性的值為 false??梢韵裣旅?/p>

這樣在事件處理程序中檢測這個(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);

});

})();

PageShowEventExample01.htm

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第412頁

394 第 13 章 事件

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

與 pageshow 事件對應(yīng)的是 pagehide 事件,該事件會(huì)在瀏覽器卸載頁面的時(shí)候觸發(fā),而且是在

unload 事件之前觸發(fā)。與 pageshow 事件一樣,pagehide 在 document 上面觸發(fā),但其事件處理程

序必須要添加到 window 對象。這個(gè)事件的 event 對象也包含 persisted 屬性,不過其用途稍有不同。

來看下面的例子。

EventUtil.addHandler(window, \"pagehide\", function(event){

alert(\"Hiding. Persisted? \" + event.persisted);

});

PageShowEventExample01.htm

有時(shí)候,可能需要在 pagehide 事件觸發(fā)時(shí)根據(jù) persisted 的值采取不同的操作。對于 pageshow

事件,如果頁面是從 bfcache 中加載的,那么 persisted 的值就是 true;對于 pagehide 事件,如果

頁面在卸載之后會(huì)被保存在 bfcache 中,那么 persisted 的值也會(huì)被設(shè)置為 true。因此,當(dāng)?shù)谝淮斡|

發(fā) pageshow 時(shí),persisted 的值一定是 false,而在第一次觸發(fā) pagehide 時(shí),persisted 就會(huì)變

成 true(除非頁面不會(huì)被保存在 bfcache 中)。

支持 pageshow 和 pagehide 事件的瀏覽器有 Firefox、Safari 5+、Chrome 和 Opera。IE9 及之前版

本不支持這兩個(gè)事件。

指定了 onunload 事件處理程序的頁面會(huì)被自動(dòng)排除在 bfcache 之外,即使事件

處理程序是空的。原因在于,onunload 最常用于撤銷在 onload 中所執(zhí)行的操作,

而跳過 onload 后再次顯示頁面很可能就會(huì)導(dǎo)致頁面不正常。

6. hashchange 事件

HTML5 新增了 hashchange 事件,以便在 URL 的參數(shù)列表(及 URL 中“#”號(hào)后面的所有字符串)

發(fā)生變化時(shí)通知開發(fā)人員。之所以新增這個(gè)事件,是因?yàn)樵?Ajax 應(yīng)用中,開發(fā)人員經(jīng)常要利用 URL 參

數(shù)列表來保存狀態(tài)或?qū)Ш叫畔ⅰ?/p>

必須要把 hashchange 事件處理程序添加給 window 對象,然后 URL 參數(shù)列表只要變化就會(huì)調(diào)用

它。此時(shí)的 event 對象應(yīng)該額外包含兩個(gè)屬性:oldURL 和 newURL。這兩個(gè)屬性分別保存著參數(shù)列表

變化前后的完整 URL。例如:

EventUtil.addHandler(window, \"hashchange\", function(event){

alert(\"Old URL: \" + event.oldURL + \"\

New URL: \" + event.newURL);

});

HashChangeEventExample01.htm

支持 hashchange 事件的瀏覽器有 IE8+、Firefox 3.6+、Safari 5+、Chrome 和 Opera 10.6+。在這些

瀏覽器中,只有 Firefox 6+、Chrome 和 Opera 支持 oldURL 和 newURL 屬性。為此,最好是使用 location

對象來確定當(dāng)前的參數(shù)列表。

EventUtil.addHandler(window, \"hashchange\", function(event){

alert(\"Current hash: \" + location.hash);

});

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第413頁

13.4 事件類型 395

14

2

3

17

18

13

19

7

8

9

10

11

12

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

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

如果 IE8 是在 IE7 文檔模式下運(yùn)行,即使功能無效它也會(huì)返回 true。為解決這個(gè)問題,可以使用

以下這個(gè)更穩(wěn)妥的檢測方式:

var isSupported = (\"onhashchange\" in window) && (document.documentMode ===

undefined || document.documentMode > 7);

13.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),以涵蓋不斷增長的設(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 頭朝下;但這種模式至今

尚未得到支持。圖 13-10 展示了 window.orientation 的每個(gè)值的含義。

圖 13-10

只要用戶改變了設(shè)備的查看模式,就會(huì)觸發(fā) orientationchange 事件。此時(shí)的 event 對象不包

含任何有價(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;

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第414頁

396 第 13 章 事件

EventUtil.addHandler(window, \"orientationchange\", function(event){

div.innerHTML = \"Current orientation is \" + window.orientation;

});

});

OrientationChangeEventExample01.htm

在這個(gè)例子中,當(dāng)觸發(fā) load 事件時(shí)會(huì)顯示最初的方向信息。然后,添加了處理 orientationchange

事件的處理程序。只要發(fā)生這個(gè)事件,就會(huì)有表示新方向的信息更新頁面中的消息。

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

由于可以將 orientationchange 看成 window 事件,所以也可以通過指定

<body>元素的 onorientationchange 特性來指定事件處理程序。

2. MozOrientation 事件

Firefox 3.6 為檢測設(shè)備的方向引入了一個(gè)名為 MozOrientation 的新事件。(前綴 Moz 表示這是特

定于瀏覽器開發(fā)商的事件,不是標(biāo)準(zhǔn)事件。)當(dāng)設(shè)備的加速計(jì)檢測到設(shè)備方向改變時(shí),就會(huì)觸發(fā)這個(gè)事

件。但這個(gè)事件與 iOS 中的 orientationchange 事件不同,該事件只能提供一個(gè)平面的方向變化。由

于 MozOrientation 事件是在 window 對象上觸發(fā)的,所以可以使用以下代碼來處理。

EventUtil.addHandler(window, \"MozOrientation\", function(event){

//響應(yīng)事件

});

此時(shí)的 event 對象包含三個(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 軸檢測垂直加速度度,1 表示靜止不動(dòng),在設(shè)備移動(dòng)時(shí)值

會(huì)減小。(失重狀態(tài)下值為 0。)以下是輸出這三個(gè)值的一個(gè)簡單的例子。

EventUtil.addHandler(window, \"MozOrientation\", function(event){

var output = document.getElementById(\"output\");

output.innerHTML = \"X=\" + event.x + \", Y=\" + event.y + \", Z=\" + event.z +\"<br>\";

});

MozOrientationEventExample01.htm

只有帶加速計(jì)的設(shè)備才支持 MozOrientation 事件,包括 Macbook、Lenovo Thinkpad、Windows

Mobile 和 Android 設(shè)備。請大家注意,這是一個(gè)實(shí)驗(yàn)性 API,將來可能會(huì)變(可能會(huì)被其他事件取代)。

3. deviceorientation 事件

本質(zhì)上,DeviceOrientation Event 規(guī)范定義的 deviceorientation 事件與 MozOrientation 事件類

似。它也是在加速計(jì)檢測到設(shè)備方向變化時(shí)在 window 對象上觸發(fā),而且具有與 MozOrientation 事件

相同的支持限制。不過,deviceorientation 事件的意圖是告訴開發(fā)人員設(shè)備在空間中朝向哪兒,而

不是如何移動(dòng)。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第415頁

13.4 事件類型 397

14

2

3

17

18

13

19

7

8

9

10

11

12

設(shè)備在三維空間中是靠 x、y 和 z 軸來定位的。當(dāng)設(shè)備靜止放在水平表面上時(shí),這三個(gè)值都是 0。x

軸方向是從左往右,y 軸方向是從下往上,z 軸方向是從后往前(參見圖 13-11)。

圖 13-11

觸發(fā) deviceorientation 事件時(shí),事件對象中包含著每個(gè)軸相對于設(shè)備靜止?fàn)顟B(tài)下發(fā)生變化的信

息。事件對象包含以下 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è)絕對值。

? compassCalibrated:布爾值,表示設(shè)備的指南針是否校準(zhǔn)過。

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

下面是一個(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>\";

});

DeviceOrientationEventExample01.htm

通過這些信息,可以響應(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)\";

});

DeviceOrientationEventExample01.htm

x

z

y

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第416頁

398 第 13 章 事件

圖 13-12

這個(gè)例子只能在移動(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 變換使用了舍入之后的值。

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

WebKit。

4. devicemotion 事件

DeviceOrientation Event 規(guī)范還定義了一個(gè) devicemotion 事件。這個(gè)事件是要告訴開發(fā)人員設(shè)備

什么時(shí)候移動(dòng),而不僅僅是設(shè)備方向如何改變。例如,通過 devicemotion 能夠檢測到設(shè)備是不是正在

往下掉,或者是不是被走著的人拿在手里。

觸發(fā) devicemotion 事件時(shí),事件對象包含以下屬性。

? acceleration:一個(gè)包含 x、y 和 z 屬性的對象,在不考慮重力的情況下,告訴你在每個(gè)方向

上的加速度。

? accelerationIncludingGravity:一個(gè)包含 x、y 和 z 屬性的對象,在考慮 z 軸自然重力加

速度的情況下,告訴你在每個(gè)方向上的加速度。

? interval:以毫秒表示的時(shí)間值,必須在另一個(gè) devicemotion 事件觸發(fā)前傳入。這個(gè)值在每

個(gè)事件中應(yīng)該是一個(gè)常量。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第417頁

13.4 事件類型 399

14

2

3

17

18

13

19

7

8

9

10

11

12

? rotationRate:一個(gè)包含表示方向的 alpha、beta 和 gamma 屬性的對象。

如果讀取不到 acceleration、accelerationIncludingGravity 和 rotationRate 值,則它們

的值為 null。因此,在使用這三個(gè)屬性之前,應(yīng)該先檢測確定它們的值不是 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;

}

});

DeviceMotionEventExample01.htm

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

devicemotion 事件。

13.4.9 觸摸與手勢事件

iOS 版 Safari 為了向開發(fā)人員傳達(dá)一些特殊信息,新增了一些專有事件。因?yàn)?iOS 設(shè)備既沒有鼠標(biāo)

也沒有鍵盤,所以在為移動(dòng) Safari 開發(fā)交互性網(wǎng)頁時(shí),常規(guī)的鼠標(biāo)和鍵盤事件根本不夠用。隨著 Android

中的 WebKit 的加入,很多這樣的專有事件變成了事實(shí)標(biāo)準(zhǔn),導(dǎo)致 W3C 開始制定 Touch Events 規(guī)范(參

見 https://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html)。以下介紹的事件只針對觸摸設(shè)備。

1. 觸摸事件

包含 iOS 2.0 軟件的 iPhone 3G 發(fā)布時(shí),也包含了一個(gè)新版本的 Safari 瀏覽器。這款新的移動(dòng) Safari

提供了一些與觸摸(touch)操作相關(guān)的新事件。后來,Android 上的瀏覽器也實(shí)現(xiàn)了相同的事件。觸摸

事件會(huì)在用戶手指放在屏幕上面時(shí)、在屏幕上滑動(dòng)時(shí)或從屏幕上移開時(shí)觸發(fā)。具體來說,有以下幾個(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 對象都提供了在鼠標(biāo)事件中常見的屬性:

bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、

ctrlKey 和 metaKey。

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

? touches:表示當(dāng)前跟蹤的觸摸操作的 Touch 對象的數(shù)組。

? targetTouchs:特定于事件目標(biāo)的 Touch 對象的數(shù)組。

? changeTouches:表示自上次觸摸以來發(fā)生了什么改變的 Touch 對象的數(shù)組。

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

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第418頁

400 第 13 章 事件

? clientX:觸摸目標(biāo)在視口中的 x 坐標(biāo)。

? clientY:觸摸目標(biāo)在視口中的 y 坐標(biāo)。

? identifier:標(biāo)識(shí)觸摸的唯一 ID。

? pageX:觸摸目標(biāo)在頁面中的 x 坐標(biāo)。

? pageY:觸摸目標(biāo)在頁面中的 y 坐標(biāo)。

? screenX:觸摸目標(biāo)在屏幕中的 x 坐標(biāo)。

? screenY:觸摸目標(biāo)在屏幕中的 y 坐標(biāo)。

? target:觸摸的 DOM 節(jié)點(diǎn)目標(biāo)。

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

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

TouchEventsExample01.htm

以上代碼會(huì)跟蹤屏幕上發(fā)生的一次觸摸操作。為簡單起見,只會(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)頁面),然后輸出觸摸操作的變化

信息。而 touchend 事件則會(huì)輸出有關(guān)觸摸操作的最終信息。注意,在 touchend 事件發(fā)生時(shí),touches

集合中就沒有任何 Touch 對象了,因?yàn)椴淮嬖诨顒?dòng)的觸摸操作;此時(shí),就必須轉(zhuǎn)而使用 changeTouchs

集合。

這些事件會(huì)在文檔的所有元素上面觸發(fā),因而可以分別操作頁面的不同部分。在觸摸屏幕上的元素

時(shí),這些事件(包括鼠標(biāo)事件)發(fā)生的順序如下:

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第419頁

13.4 事件類型 401

14

2

3

17

18

13

19

7

8

9

10

11

12

(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. 手勢事件

iOS 2.0 中的 Safari 還引入了一組手勢事件。當(dāng)兩個(gè)手指觸摸屏幕時(shí)就會(huì)產(chǎn)生手勢,手勢通常會(huì)改變

顯示項(xiàng)的大小,或者旋轉(zhuǎn)顯示項(xiàng)。有三個(gè)手勢事件,分別介紹如下。

? 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ā)手勢事件(這個(gè)元素就是目標(biāo))。由于這些事

件冒泡,所以將事件處理程序放在文檔上也可以處理所有手勢事件。此時(shí),事件的目標(biāo)就是兩個(gè)手指都

位于其范圍內(nèi)的那個(gè)元素。

觸摸事件和手勢事件之間存在某種關(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è)手勢事件的 event 對象都包含著標(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 開始,并隨距離拉大而

增長,隨距離縮短而減小。

下面是使用手勢事件的一個(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\":

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第420頁

402 第 13 章 事件

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

GestureEventsExample01.htm

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

函數(shù)輸出每個(gè)事件的相關(guān)信息。

觸摸事件也會(huì)返回 rotation 和 scale 屬性,但這兩個(gè)屬性只會(huì)在兩個(gè)手指與

屏幕保持接觸時(shí)才會(huì)發(fā)生變化。一般來說,使用基于兩個(gè)手指的手勢事件,要比管理

觸摸事件中的所有交互要容易得多。

13.5 內(nèi)存和性能

由于事件處理程序可以為現(xiàn)代 Web 應(yīng)用程序提供交互能力,因此許多開發(fā)人員會(huì)不分青紅皂白地

向頁面中添加大量的處理程序。在創(chuàng)建 GUI 的語言(如 C#)中,為 GUI 中的每個(gè)按鈕添加一個(gè) onclick

事件處理程序是司空見慣的事,而且這樣做也不會(huì)導(dǎo)致什么問題??墒窃?JavaScript 中,添加到頁面上

的事件處理程序數(shù)量將直接關(guān)系到頁面的整體運(yùn)行性能。導(dǎo)致這一問題的原因是多方面的。首先,每個(gè)

函數(shù)都是對象,都會(huì)占用內(nèi)存;內(nèi)存中的對象越多,性能就越差。其次,必須事先指定所有事件處理程

序而導(dǎo)致的 DOM 訪問次數(shù),會(huì)延遲整個(gè)頁面的交互就緒時(shí)間。事實(shí)上,從如何利用好事件處理程序的

角度出發(fā),還是有一些方法能夠提升性能的。

13.5.1 事件委托

對“事件處理程序過多”問題的解決方案就是事件委托。事件委托利用了事件冒泡,只指定一個(gè)事

件處理程序,就可以管理某一類型的所有事件。例如,click 事件會(huì)一直冒泡到 document 層次。也就

是說,我們可以為整個(gè)頁面指定一個(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>

EventDelegationExample01.htm

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

件處理程序。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第421頁

13.5 內(nèi)存和性能 403

14

2

3

17

18

13

19

7

8

9

10

11

12

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

});

如果在一個(gè)復(fù)雜的 Web 應(yīng)用程序中,對所有可單擊的元素都采用這種方式,那么結(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.href = \"http://www.wrox.com\";

break;

case \"sayHi\":

alert(\"hi\");

break;

}

});

EventDelegationExample01.htm

在這段代碼里,我們使用事件委托只為<ul>元素添加了一個(gè) onclick 事件處理程序。由于所有列

表項(xiàng)都是這個(gè)元素的子節(jié)點(diǎn),而且它們的事件會(huì)冒泡,所以單擊事件最終會(huì)被這個(gè)函數(shù)處理。我們知道,

事件目標(biāo)是被單擊的列表項(xiàng),故而可以通過檢測 id 屬性來決定采取適當(dāng)?shù)牟僮?。與前面未使用事件委

托的代碼比一比,會(huì)發(fā)現(xiàn)這段代碼的事前消耗更低,因?yàn)橹蝗〉昧艘粋€(gè) DOM 元素,只添加了一個(gè)事件

處理程序。雖然對用戶來說最終的結(jié)果相同,但這種技術(shù)需要占用的內(nèi)存更少。所有用到按鈕的事件(多

數(shù)鼠標(biāo)事件和鍵盤事件)都適合采用事件委托技術(shù)。

如果可行的話,也可以考慮為 document 對象添加一個(gè)事件處理程序,用以處理頁面上發(fā)生的某種

特定類型的事件。這樣做與采取傳統(tǒng)的做法相比具有如下優(yōu)點(diǎn)。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第422頁

404 第 13 章 事件

? document 對象很快就可以訪問,而且可以在頁面生命周期的任何時(shí)點(diǎn)上為它添加事件處理程序

(無需等待 DOMContentLoaded 或 load 事件)。換句話說,只要可單擊的元素呈現(xiàn)在頁面上,

就可以立即具備適當(dāng)?shù)墓δ堋?/p>

? 在頁面中設(shè)置事件處理程序所需的時(shí)間更少。只添加一個(gè)事件處理程序所需的 DOM 引用更少,

所花的時(shí)間也更少。

? 整個(gè)頁面占用的內(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 事件。)

13.5.2 移除事件處理程序

每當(dāng)將事件處理程序指定給元素時(shí),運(yùn)行中的瀏覽器代碼與支持頁面交互的 JavaScript 代碼之間就

會(huì)建立一個(gè)連接。這種連接越多,頁面執(zhí)行起來就越慢。如前所述,可以采用事件委托技術(shù),限制建立

的連接數(shù)量。另外,在不需要的時(shí)候移除事件處理程序,也是解決這個(gè)問題的一種方案。內(nèi)存中留有那

些過時(shí)不用的“空事件處理程序”(dangling event handler),也是造成 Web 應(yīng)用程序內(nèi)存與性能問題的

主要原因。

在兩種情況下,可能會(huì)造成上述問題。第一種情況就是從文檔中移除帶有事件處理程序的元素時(shí)。

這可能是通過純粹的 DOM 操作,例如使用 removeChild()和 replaceChild()方法,但更多地是發(fā)

生在使用 innerHTML 替換頁面中某一部分的時(shí)候。如果帶有事件處理程序的元素被 innerHTML 刪除

了,那么原來添加到元素中的事件處理程序極有可能無法被當(dāng)作垃圾回收。來看下面的例子。

<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)按鈕被從頁面中移除時(shí),它還帶著一個(gè)

事件處理程序呢。在<div>元素上設(shè)置 innerHTML 可以把按鈕移走,但事件處理程序仍然與按鈕保持

著引用關(guān)系。有的瀏覽器(尤其是 IE)在這種情況下不會(huì)作出恰當(dāng)?shù)靥幚?,它們很有可能?huì)將對元素和

對事件處理程序的引用都保存在內(nèi)存中。如果你知道某個(gè)元素即將被移除,那么最好手工移除事件處理

程序,如下面的例子所示。

<div id=\"myDiv\">

<input type=\"button\" value=\"Click Me\" id=\"myBtn\">

</div>

<script type=\"text/javascript\">

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

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第423頁

13.6 模擬事件 405

14

2

3

17

18

13

19

7

8

9

10

11

12

btn.onclick = function(){

//先執(zhí)行某些操作

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

document.getElementById(\"myDiv\").innerHTML = \"Processing...\";

};

</script>

在此,我們在設(shè)置<div>的 innerHTML 屬性之前,先移除了按鈕的事件處理程序。這樣就確保了

內(nèi)存可以被再次利用,而從 DOM 中移除按鈕也做到了干凈利索。

注意,在事件處理程序中刪除按鈕也能阻止事件冒泡。目標(biāo)元素在文檔中是事件冒泡的前提。

采用事件委托也有助于解決這個(gè)問題。如果事先知道將來有可能使用innerHTML

替換掉頁面中的某一部分,那么就可以不直接把事件處理程序添加到該部分的元素

中。而通過把事件處理程序指定給較高層次的元素,同樣能夠處理該區(qū)域中的事件。

導(dǎo)致“空事件處理程序”的另一種情況,就是卸載頁面的時(shí)候。毫不奇怪,IE8 及更早版本在這種

情況下依然是問題最多的瀏覽器,盡管其他瀏覽器或多或少也有類似的問題。如果在頁面被卸載之前沒

有清理干凈事件處理程序,那它們就會(huì)滯留在內(nèi)存中。每次加載完頁面再卸載頁面時(shí)(可能是在兩個(gè)頁

面間來回切換,也可以是單擊了“刷新”按鈕),內(nèi)存中滯留的對象數(shù)目就會(huì)增加,因?yàn)槭录幚沓绦?/p>

占用的內(nèi)存并沒有被釋放。

一般來說,最好的做法是在頁面卸載之前,先通過 onunload 事件處理程序移除所有事件處理程序。

在此,事件委托技術(shù)再次表現(xiàn)出它的優(yōu)勢——需要跟蹤的事件處理程序越少,移除它們就越容易。對這

種類似撤銷的操作,我們可以把它想象成:只要是通過 onload 事件處理程序添加的東西,最后都要通

過 onunload 事件處理程序?qū)⑺鼈円瞥?/p>

不要忘了,使用 onunload 事件處理程序意味著頁面不會(huì)被緩存在 bfcache 中。

如果你在意這個(gè)問題,那么就只能在 IE 中通過 onunload 來移除事件處理程序了。

13.6 模擬事件

事件,就是網(wǎng)頁中某個(gè)特別值得關(guān)注的瞬間。事件經(jīng)常由用戶操作或通過其他瀏覽器功能來觸發(fā)。

但很少有人知道,也可以使用 JavaScript 在任意時(shí)刻來觸發(fā)特定的事件,而此時(shí)的事件就如同瀏覽器創(chuàng)

建的事件一樣。也就是說,這些事件該冒泡還會(huì)冒泡,而且照樣能夠?qū)е聻g覽器執(zhí)行已經(jīng)指定的處理它

們的事件處理程序。在測試 Web 應(yīng)用程序,模擬觸發(fā)事件是一種極其有用的技術(shù)。DOM2 級(jí)規(guī)范為此

規(guī)定了模擬特定事件的方式,IE9、Opera、Firefox、Chrome 和 Safari 都支持這種方式。IE 有它自己模擬

事件的方式。

13.6.1 DOM中的事件模擬

可以在 document 對象上使用 createEvent()方法創(chuàng)建 event 對象。這個(gè)方法接收一個(gè)參數(shù),即

表示要?jiǎng)?chuàng)建的事件類型的字符串。在 DOM2 級(jí)中,所有這些字符串都使用英文復(fù)數(shù)形式,而在 DOM3

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第424頁

406 第 13 章 事件

級(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 事件。沒有對應(yīng)的 DOM3 級(jí)事件(HTML 事件被分散到其他類

別中)。

要注意的是,“DOM2 級(jí)事件”并沒有專門規(guī)定鍵盤事件,后來的“DOM3 級(jí)事件”中才正式將其

作為一種事件給出規(guī)定。IE9 是目前唯一支持 DOM3 級(jí)鍵盤事件的瀏覽器。不過,在其他瀏覽器中,在

現(xiàn)有方法的基礎(chǔ)上,可以通過幾種方式來模擬鍵盤事件。

在創(chuàng)建了 event 對象之后,還需要使用與事件有關(guān)的信息對其進(jìn)行初始化。每種類型的 event 對

象都有一個(gè)特殊的方法,為它傳入適當(dāng)?shù)臄?shù)據(jù)就可以初始化該 event 對象。不同類型的這個(gè)方法的名

字也不相同,具體要取決于 createEvent()中使用的參數(shù)。

模擬事件的最后一步就是觸發(fā)事件。這一步需要使用 dispatchEvent()方法,所有支持事件的

DOM 節(jié)點(diǎn)都支持這個(gè)方法。調(diào)用 dispatchEvent()方法時(shí),需要傳入一個(gè)參數(shù),即表示要觸發(fā)事件

的 event 對象。觸發(fā)事件之后,該事件就躋身“官方事件”之列了,因而能夠照樣冒泡并引發(fā)相應(yīng)事

件處理程序的執(zhí)行。

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

創(chuàng)建新的鼠標(biāo)事件對象并為其指定必要的信息,就可以模擬鼠標(biāo)事件。創(chuàng)建鼠標(biāo)事件對象的方法是

為 createEvent()傳入字符串\"MouseEvents\"。返回的對象有一個(gè)名為 initMouseEvent()方法,

用于指定與該鼠標(biāo)事件有關(guān)的信息。這個(gè)方法接收 15 個(gè)參數(shù),分別與鼠標(biāo)事件中每個(gè)典型的屬性一一

對應(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ù)):事件相對于屏幕的 X 坐標(biāo)。

? screenY(整數(shù)):事件相對于屏幕的 Y 坐標(biāo)。

? clientX(整數(shù)):事件相對于視口的 X 坐標(biāo)。

? clientY(整數(shù)):事件想對于視口的 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(對象):表示與事件相關(guān)的對象。這個(gè)參數(shù)只在模擬 mouseover 或 mouseout

時(shí)使用。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第425頁

13.6 模擬事件 407

14

2

3

17

18

13

19

7

8

9

10

11

12

顯而易見,initMouseEvent()方法的這些參數(shù)是與鼠標(biāo)事件的 event 對象所包含的屬性一一對

應(yīng)的。其中,前 4 個(gè)參數(shù)對正確地激發(fā)事件至關(guān)重要,因?yàn)闉g覽器要用到這些參數(shù);而剩下的所有參數(shù)

只有在事件處理程序中才會(huì)用到。當(dāng)把 event 對象傳給 dispatchEvent()方法時(shí),這個(gè)對象的 target

屬性會(huì)自動(dòng)設(shè)置。下面,我們就通過一個(gè)例子來了解如何模擬對按鈕的單擊事件。

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

//創(chuàng)建事件對象

var event = document.createEvent(\"MouseEvents\");

//初始化事件對象

event.initMouseEvent(\"click\", true, true, document.defaultView, 0, 0, 0, 0, 0,

false, false, false, false, 0, null);

//觸發(fā)事件

btn.dispatchEvent(event);

SimulateDOMClickExample01.htm

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

2. 模擬鍵盤事件

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

思路可循?!癉OM2 級(jí)事件”的草案中本來包含了鍵盤事件,但在定稿之前又被刪除了;Firefox 根據(jù)其

草案實(shí)現(xiàn)了鍵盤事件。需要提請大家注意的是,“DOM3 級(jí)事件”中的鍵盤事件與曾包含在“DOM2 級(jí)

事件”草案中的鍵盤事件有很大區(qū)別。

DOM3 級(jí)規(guī)定,調(diào)用 createEvent()并傳入\"KeyboardEvent\"就可以創(chuàng)建一個(gè)鍵盤事件。返回的

事件對象會(huì)包含一個(gè) initKeyEvent()方法,這個(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。

? key(布爾值):表示按下的鍵的鍵碼。

? location(整數(shù)):表示按下了哪里的鍵。0 表示默認(rèn)的主鍵盤,1 表示左,2 表示右,3 表示

數(shù)字鍵盤,4 表示移動(dòng)設(shè)備(即虛擬鍵盤),5 表示手柄。

? modifiers(字符串):空格分隔的修改鍵列表,如\"Shift\"。

? repeat(整數(shù)):在一行中按了這個(gè)鍵多少次。

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

var textbox = document.getElementById(\"myTextbox\"),

event;

//以 DOM3 級(jí)方式創(chuàng)建事件對象

if (document.implementation.hasFeature(\"KeyboardEvents\", \"3.0\")){

event = document.createEvent(\"KeyboardEvent\");

//初始化事件對象

event.initKeyboardEvent(\"keydown\", true, true, document.defaultView, \"a\",

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第426頁

408 第 13 章 事件

0, \"Shift\", 0);

}

//觸發(fā)事件

textbox.dispatchEvent(event);

SimulateDOMKeyEventExample01.htm

這個(gè)例子模擬的是按住 Shift 的同時(shí)又按下 A 鍵。在使用 document.createEvent

(\"KeyboardEvent\")之前,應(yīng)該先檢測瀏覽器是否支持 DOM3 級(jí)事件;其他瀏覽器返回一個(gè)非標(biāo)準(zhǔn)的

KeyboardEvent 對象。

在 Firefox 中,調(diào)用 createEvent()并傳入\"KeyEvents\"就可以創(chuàng)建一個(gè)鍵盤事件。返回的事件

對象會(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ù)對 keydown 和 keyup 事件有用,默認(rèn)

值為 0。

? charCode(整數(shù)):通過按鍵生成的字符的 ASCII 編碼。這個(gè)參數(shù)對 keypress 事件有用,默

認(rèn)值為 0。

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

//只適用于 Firefox

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

//創(chuàng)建事件對象

var event = document.createEvent(\"KeyEvents\");

//初始化事件對象

event.initKeyEvent(\"keypress\", true, true, document.defaultView, false, false,

false, false, 65, 65);

//觸發(fā)事件

textbox.dispatchEvent(event);

SimulateFFKeyEventExample01.htm

在 Firefox 中運(yùn)行上面的代碼,會(huì)在指定的文本框中輸入字母 A。同樣,也可以依此模擬 keyup 和

keydown 事件。

在其他瀏覽器中,則需要?jiǎng)?chuàng)建一個(gè)通用的事件,然后再向事件對象中添加鍵盤事件特有的信息。

例如:

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第427頁

13.6 模擬事件 409

14

2

3

17

18

13

19

7

8

9

10

11

12

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

//創(chuàng)建事件對象

var event = document.createEvent(\"Events\");

//初始化事件對象

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()對其進(jìn)行初始化,最后又為其添加了

鍵盤事件的具體信息。在此必須要使用通用事件,而不能使用 UI 事件,因?yàn)?UI 事件不允許向 event

對象中再添加新屬性(Safari 除外)。像這樣模擬事件雖然會(huì)觸發(fā)鍵盤事件,但卻不會(huì)向文本框中寫入文

本,這是由于無法精確模擬鍵盤事件所造成的。

3. 模擬其他事件

雖然鼠標(biāo)事件和鍵盤事件是在瀏覽器中最經(jīng)常模擬的事件,但有時(shí)候同樣需要模擬變動(dòng)事件和

HTML 事件。要模擬變動(dòng)事件,可以使用 createEvent(\"MutationEvents\")創(chuàng)建一個(gè)包含

initMutationEvent()方法的變動(dòng)事件對象。這個(gè)方法接受的參數(shù)包括:type、bubbles、

cancelable、relatedNode、preValue、newValue、attrName 和 attrChange。下面來看一個(gè)模

擬變動(dòng)事件的例子。

var event = document.createEvent(\"MutationEvents\");

event.initMutationEvent(\"DOMNodeInserted\", true, false, someNode, \"\",\"\",\"\",0);

target.dispatchEvent(event);

以上代碼模擬了 DOMNodeInserted 事件。其他變動(dòng)事件也都可以照這個(gè)樣子來模擬,只要改一改

參數(shù)就可以了。

要模擬 HTML 事件,同樣需要先創(chuàng)建一個(gè) event 對象——通過 createEvent(\"HTMLEvents\"),

然后再使用這個(gè)對象的 initEvent()方法來初始化它即可,如下面的例子所示。

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\")。返回的對象有

一個(gè)名為 initCustomEvent()的方法,接收如下 4 個(gè)參數(shù)。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第428頁

410 第 13 章 事件

? type(字符串):觸發(fā)的事件類型,例如\"keydown\"。

? bubbles(布爾值):表示事件是否應(yīng)該冒泡。

? cancelable(布爾值):表示事件是否可以取消。

? detail(對象):任意值,保存在 event 對象的 detail 屬性中。

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

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

}

SimulateDOMCustomEventExample01.htm

這個(gè)例子創(chuàng)建了一個(gè)冒泡事件\"myevent\"。而 event.detail 的值被設(shè)置成了一個(gè)簡單的字符串,

然后在<div>元素和 document 上偵聽這個(gè)事件。因?yàn)?initCustomEvent()方法已經(jīng)指定這個(gè)事件應(yīng)

該冒泡,所以瀏覽器會(huì)負(fù)責(zé)將事件向上冒泡到 document。

支持自定義 DOM 事件的瀏覽器有 IE9+和 Firefox 6+。

13.6.2 IE中的事件模擬

在 IE8 及之前版本中模擬事件與在 DOM 中模擬事件的思路相似:先創(chuàng)建 event 對象,然后為其指

定相應(yīng)的信息,然后再使用該對象來觸發(fā)事件。當(dāng)然,IE 在實(shí)現(xiàn)每個(gè)步驟時(shí)都采用了不一樣的方式。

調(diào)用 document.createEventObject()方法可以在 IE 中創(chuàng)建 event 對象。但與 DOM 方式不同

的是,這個(gè)方法不接受參數(shù),結(jié)果會(huì)返回一個(gè)通用的 event 對象。然后,你必須手工為這個(gè)對象添加

所有必要的信息(沒有方法來輔助完成這一步驟)。最后一步就是在目標(biāo)上調(diào)用 fireEvent()方法,這

個(gè)方法接受兩個(gè)參數(shù):事件處理程序的名稱和 event 對象。在調(diào)用 fireEvent()方法時(shí),會(huì)自動(dòng)為

event 對象添加 srcElement 和 type 屬性;其他屬性則都是必須通過手工添加的。換句話說,模擬任

何 IE 支持的事件都采用相同的模式。例如,下面的代碼模擬了在一個(gè)按鈕上觸發(fā) click 事件過程。

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

//創(chuàng)建事件對象

var event = document.createEventObject();

//初始化事件對象

event.screenX = 100;

event.screenY = 0;

event.clientX = 0;

event.clientY = 0;

event.ctrlKey = false;

event.altKey = false;

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第429頁

13.7 小結(jié) 411

14

2

3

17

18

13

19

7

8

9

10

11

12

event.shiftKey = false;

event.button = 0;

//觸發(fā)事件

btn.fireEvent(\"onclick\", event);

SimulateIEClickExample01.htm

這個(gè)例子先創(chuàng)建了一個(gè) event 對象,然后又用一些信息對其進(jìn)行了初始化。注意,這里可以為對

象隨意添加屬性,不會(huì)有任何限制——即使添加的屬性 IE8 及更早版本并不支持也無所謂。在此添加的

屬性對事件沒有什么影響,因?yàn)橹挥惺录幚沓绦虿艜?huì)用到它們。

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

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

//創(chuàng)建事件對象

var event = document.createEventObject();

//初始化事件對象

event.altKey = false;

event.ctrlKey = false;

event.shiftKey = false;

event.keyCode = 65;

//觸發(fā)事件

textbox.fireEvent(\"onkeypress\", event);

SimulateIEKeyEventExample01.htm

由于鼠標(biāo)事件、鍵盤事件以及其他事件的 event 對象并沒有什么不同,所以可以使用通用對象來

觸發(fā)任何類型的事件。不過,正如在DOM中模擬鍵盤事件一樣,運(yùn)行這個(gè)例子也不會(huì)因模擬了keypress

而在文本框中看到任何字符,即使觸發(fā)了事件處理程序也沒有用。

13.7 小結(jié)

事件是將 JavaScript 與網(wǎng)頁聯(lián)系在一起的主要方式?!癉OM3 級(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è)頁面中事件處理程序的數(shù)量,數(shù)量太多會(huì)導(dǎo)致占用大量內(nèi)存,而且也會(huì)讓用戶

感覺頁面反應(yīng)不夠靈敏。

? 建立在事件冒泡機(jī)制之上的事件委托技術(shù),可以有效地減少事件處理程序的數(shù)量。

? 建議在瀏覽器卸載頁面之前移除頁面中的所有事件處理程序。

可以使用 JavaScript 在瀏覽器中模擬事件?!癉OM2 級(jí)事件”和“DOM3 級(jí)事件”規(guī)范規(guī)定了模擬事

件的方法,為模擬各種有定義的事件提供了方便。此外,通過組合使用一些技術(shù),還可以在某種程度上

模擬鍵盤事件。IE8 及之前版本同樣支持事件模擬,只不過模擬的過程有些差異。

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

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第430頁

412 第 14 章 表單腳本

表 單 腳 本

本章內(nèi)容

? 理解表單

? 文本框驗(yàn)證與交互

? 使用其他表單控制

avaScript 最初的一個(gè)應(yīng)用,就是分擔(dān)服務(wù)器處理表單的責(zé)任,打破處處依賴服務(wù)器的局面。盡

管目前的 Web 和 JavaScript 已經(jīng)有了長足的發(fā)展,但 Web 表單的變化并不明顯。由于 Web 表單

沒有為許多常見任務(wù)提供現(xiàn)成的解決手段,很多開發(fā)人員不僅會(huì)在驗(yàn)證表單時(shí)使用 JavaScirpt,而且還

增強(qiáng)了一些標(biāo)準(zhǔn)表單控件的默認(rèn)行為。

14.1 表單的基礎(chǔ)知識(shí)

在 HTML 中,表單是由<form>元素來表示的,而在 JavaScript 中,表單對應(yīng)的則是 HTMLFormElement 類型。HTMLFormElement 繼承了 HTMLElement,因而與其他 HTML 元素具有相同的默認(rèn)屬

性。不過,HTMLFormElement 也有它自己下列獨(dú)有的屬性和方法。

? acceptCharset:服務(wù)器能夠處理的字符集;等價(jià)于 HTML 中的 accept-charset 特性。

? action:接受請求的 URL;等價(jià)于 HTML 中的 action 特性。

? elements:表單中所有控件的集合(HTMLCollection)。

? enctype:請求的編碼類型;等價(jià)于 HTML 中的 enctype 特性。

? length:表單中控件的數(shù)量。

? method:要發(fā)送的 HTTP 請求類型,通常是\"get\"或\"post\";等價(jià)于 HTML 的 method 特性。

? name:表單的名稱;等價(jià)于 HTML 的 name 特性。

? reset():將所有表單域重置為默認(rèn)值。

? submit():提交表單。

? target:用于發(fā)送請求和接收響應(yīng)的窗口名稱;等價(jià)于 HTML 的 target 特性。

取得<form>元素引用的方式有好幾種。其中最常見的方式就是將它看成與其他元素一樣,并為其

添加 id 特性,然后再像下面這樣使用 getElementById()方法找到它。

var form = document.getElementById(\"form1\");

其次,通過 document.forms 可以取得頁面中所有的表單。在這個(gè)集合中,可以通過數(shù)值索引或

name 值來取得特定的表單,如下面的例子所示。

J

第 14 章

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第431頁

14.1 表單的基礎(chǔ)知識(shí) 413

14

2

3

4

5

13

6

7

8

9

10

11

12

var firstForm = document.forms[0]; //取得頁面中的第一個(gè)表單

var myForm = document.forms[\"form2\"]; //取得頁面中名稱為\"form2\"的表單

另外,在較早的瀏覽器或者那些支持向后兼容的瀏覽器中,也會(huì)把每個(gè)設(shè)置了 name 特性的表單作

為屬性保存在 document 對象中。例如,通過 document.form2 可以訪問到名為\"form2\"的表單。不

過,我們不推薦使用這種方式:一是容易出錯(cuò),二是將來的瀏覽器可能會(huì)不支持。

注意,可以同時(shí)為表單指定 id 和 name 屬性,但它們的值不一定相同。

14.1.1 提交表單

用戶單擊提交按鈕或圖像按鈕時(shí),就會(huì)提交表單。使用<input>或<button>都可以定義提交按鈕,

只要將其 type 特性的值設(shè)置為\"submit\"即可,而圖像按鈕則是通過將<input>的 type 特性值設(shè)置為

\"image\"來定義的。因此,只要我們單擊以下代碼生成的按鈕,就可以提交表單。

<!-- 通用提交按鈕 -->

<input type=\"submit\" value=\"Submit Form\">

<!-- 自定義提交按鈕 -->

<button type=\"submit\">Submit Form</button>

<!-- 圖像按鈕 -->

<input type=\"image\" src=\"graphic.gif\">

只要表單中存在上面列出的任何一種按鈕,那么在相應(yīng)表單控件擁有焦點(diǎn)的情況下,按回車鍵就可

以提交該表單。(textarea 是一個(gè)例外,在文本區(qū)中回車會(huì)換行。)如果表單里沒有提交按鈕,按回車

鍵不會(huì)提交表單。

以這種方式提交表單時(shí),瀏覽器會(huì)在將請求發(fā)送給服務(wù)器之前觸發(fā) submit 事件。這樣,我們就有

機(jī)會(huì)驗(yàn)證表單數(shù)據(jù),并據(jù)以決定是否允許表單提交。阻止這個(gè)事件的默認(rèn)行為就可以取消表單提交。例

如,下列代碼會(huì)阻止表單提交。

var form = document.getElementById(\"myForm\");

EventUtil.addHandler(form, \"submit\", function(event){

//取得事件對象

event = EventUtil.getEvent(event);

//阻止默認(rèn)事件

EventUtil.preventDefault(event);

});

這里使用了第 13 章定義的 EventUtil 對象,以便跨瀏覽器處理事件。調(diào)用 prevetnDefault()

方法阻止了表單提交。一般來說,在表單數(shù)據(jù)無效而不能發(fā)送給服務(wù)器時(shí),可以使用這一技術(shù)。

在 JavaScript 中,以編程方式調(diào)用 submit()方法也可以提交表單。而且,這種方式無需表單包含

提交按鈕,任何時(shí)候都可以正常提交表單。來看一個(gè)例子。

var form = document.getElementById(\"myForm\");

//提交表單

form.submit();

在以調(diào)用 submit()方法的形式提交表單時(shí),不會(huì)觸發(fā) submit 事件,因此要記得在調(diào)用此方法之

前先驗(yàn)證表單數(shù)據(jù)。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第432頁

414 第 14 章 表單腳本

提交表單時(shí)可能出現(xiàn)的最大問題,就是重復(fù)提交表單。在第一次提交表單后,如果長時(shí)間沒有反

應(yīng),用戶可能會(huì)變得不耐煩。這時(shí)候,他們也許會(huì)反復(fù)單擊提交按鈕。結(jié)果往往很麻煩(因?yàn)榉?wù)器

要處理重復(fù)的請求),或者會(huì)造成錯(cuò)誤(如果用戶是下訂單,那么可能會(huì)多訂好幾份)。解決這一問題

的辦法有兩個(gè):在第一次提交表單后就禁用提交按鈕,或者利用 onsubmit 事件處理程序取消后續(xù)的

表單提交操作。

14.1.2 重置表單

在用戶單擊重置按鈕時(shí),表單會(huì)被重置。使用 type 特性值為\"reset\"的<input>或<button>都

可以創(chuàng)建重置按鈕,如下面的例子所示。

<!-- 通用重置按鈕 -->

<input type=\"reset\" value=\"Reset Form\">

<!-- 自定義重置按鈕 -->

<button type=\"reset\">Reset Form</button>

這兩個(gè)按鈕都可以用來重置表單。在重置表單時(shí),所有表單字段都會(huì)恢復(fù)到頁面剛加載完畢時(shí)的初

始值。如果某個(gè)字段的初始值為空,就會(huì)恢復(fù)為空;而帶有默認(rèn)值的字段,也會(huì)恢復(fù)為默認(rèn)值。

用戶單擊重置按鈕重置表單時(shí),會(huì)觸發(fā) reset 事件。利用這個(gè)機(jī)會(huì),我們可以在必要時(shí)取消重置

操作。例如,下面展示了阻止重置表單的代碼。

var form = document.getElementById(\"myForm\");

EventUtil.addHandler(form, \"reset\", function(event){

//取得事件對象

event = EventUtil.getEvent(event);

//阻止表單重置

EventUtil.preventDefault(event);

});

與提交表單一樣,也可以通過 JavaScript 來重置表單,如下面的例子所示。

var form = document.getElementById(\"myForm\");

//重置表單

form.reset();

與調(diào)用 submit()方法不同,調(diào)用 reset()方法會(huì)像單擊重置按鈕一樣觸發(fā) reset 事件。

在 Web 表單設(shè)計(jì)中,重置表單通常意味著對已經(jīng)填寫的數(shù)據(jù)不滿意。重置表單

經(jīng)常會(huì)導(dǎo)致用戶摸不著頭腦,如果意外地觸發(fā)了表單重置事件,那么用戶甚至?xí)軔?/p>

火。事實(shí)上,重置表單的需求是很少見的。更常見的做法是提供一個(gè)取消按鈕,讓用

戶能夠回到前一個(gè)頁面,而不是不分青紅皂白地重置表單中的所有值。

14.1.3 表單字段

可以像訪問頁面中的其他元素一樣,使用原生 DOM 方法訪問表單元素。此外,每個(gè)表單都有

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第433頁

14.1 表單的基礎(chǔ)知識(shí) 415

14

2

3

4

5

13

6

7

8

9

10

11

12

elements 屬性,該屬性是表單中所有表單元素(字段)的集合。這個(gè) elements 集合是一個(gè)有序列表,

其中包含著表單中的所有字段,例如<input>、<textarea>、<button>和<fieldset>。每個(gè)表單字

段在 elements 集合中的順序,與它們出現(xiàn)在標(biāo)記中的順序相同,可以按照位置和 name 特性來訪問它

們。下面來看一個(gè)例子。

var form = document.getElementById(\"form1\");

//取得表單中的第一個(gè)字段

var field1 = form.elements[0];

//取得名為\"textbox1\"的字段

var field2 = form.elements[\"textbox1\"];

//取得表單中包含的字段的數(shù)量

var fieldCount = form.elements.length;

如果有多個(gè)表單控件都在使用一個(gè) name(如單選按鈕),那么就會(huì)返回以該 name 命名的一個(gè)

NodeList。例如,以下面的 HTML 代碼片段為例。

<form method=\"post\" id=\"myForm\">

<ul>

<li><input type=\"radio\" name=\"color\" value=\"red\">Red</li>

<li><input type=\"radio\" name=\"color\" value=\"green\">Green</li>

<li><input type=\"radio\" name=\"color\" value=\"blue\">Blue</li>

</ul>

</form>

FormFieldsExample01.htm

在這個(gè) HTML 表單中,有 3 個(gè)單選按鈕,它們的 name 都是\"color\",意味著這 3 個(gè)字段是一起的。

在訪問 elements[\"color\"]時(shí),就會(huì)返回一個(gè) NodeList,其中包含這 3 個(gè)元素;不過,如果訪問

elements[0],則只會(huì)返回第一個(gè)元素。來看下面的例子。

var form = document.getElementById(\"myForm\");

var colorFields = form.elements[\"color\"];

alert(colorFields.length); //3

var firstColorField = colorFields[0];

var firstFormField = form.elements[0];

alert(firstColorField === firstFormField); //true

FormFieldsExample01.htm

以上代碼顯示,通過 form.elements[0]訪問到的第一個(gè)表單字段,與包含在 form.elements

[\"color\"]中的第一個(gè)元素相同。

也可以通過訪問表單的屬性來訪問元素,例如 form[0]可以取得第一個(gè)表單字

段,而 form[\"color\"]則可以取得第一個(gè)命名字段。這些屬性與通過 elements 集

合訪問到的元素是相同的。但是,我們應(yīng)該盡可能使用 elements,通過表單屬性訪

問元素只是為了與舊瀏覽器向后兼容而保留的一種過渡方式。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第434頁

416 第 14 章 表單腳本

1. 共有的表單字段屬性

除了<fieldset>元素之外,所有表單字段都擁有相同的一組屬性。由于<input>類型可以表示多

種表單字段,因此有些屬性只適用于某些字段,但還有一些屬性是所有字段所共有的。表單字段共有的

屬性如下。

? disabled:布爾值,表示當(dāng)前字段是否被禁用。

? form:指向當(dāng)前字段所屬表單的指針;只讀。

? name:當(dāng)前字段的名稱。

? readOnly:布爾值,表示當(dāng)前字段是否只讀。

? tabIndex:表示當(dāng)前字段的切換(tab)序號(hào)。

? type:當(dāng)前字段的類型,如\"checkbox\"、\"radio\",等等。

? value:當(dāng)前字段將被提交給服務(wù)器的值。對文件字段來說,這個(gè)屬性是只讀的,包含著文件

在計(jì)算機(jī)中的路徑。

除了 form 屬性之外,可以通過 JavaScript 動(dòng)態(tài)修改其他任何屬性。來看下面的例子:

var form = document.getElementById(\"myForm\");

var field = form.elements[0];

//修改 value 屬性

field.value = \"Another value\";

//檢查 form 屬性的值

alert(field.form === form); //true

//把焦點(diǎn)設(shè)置到當(dāng)前字段

field.focus();

//禁用當(dāng)前字段

field.disabled = true;

//修改 type 屬性(不推薦,但對<input>來說是可行的)

field.type = \"checkbox\";

能夠動(dòng)態(tài)修改表單字段屬性,意味著我們可以在任何時(shí)候,以任何方式來動(dòng)態(tài)操作表單。例如,很

多用戶可能會(huì)重復(fù)單擊表單的提交按鈕。在涉及信用卡消費(fèi)時(shí),這就是個(gè)問題:因?yàn)闀?huì)導(dǎo)致費(fèi)用翻番。

為此,最常見的解決方案,就是在第一次單擊后就禁用提交按鈕。只要偵聽 submit 事件,并在該事件

發(fā)生時(shí)禁用提交按鈕即可。以下就是這樣一個(gè)例子。

//避免多次提交表單

EventUtil.addHandler(form, \"submit\", function(event){

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

//取得提交按鈕

var btn = target.elements[\"submit-btn\"];

//禁用它

btn.disabled = true;

});

FormFieldsExample02.htm

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第435頁

14.1 表單的基礎(chǔ)知識(shí) 417

14

2

3

4

5

13

6

7

8

9

10

11

12

以上代碼為表單的 submit 事件添加了一個(gè)事件處理程序。事件觸發(fā)后,代碼取得了提交按鈕

并將其 disabled 屬性設(shè)置為 true。注意,不能通過 onclick 事件處理程序來實(shí)現(xiàn)這個(gè)功能,原

因是不同瀏覽器之間存在“時(shí)差”:有的瀏覽器會(huì)在觸發(fā)表單的 submit 事件之前觸發(fā) click 事件,

而有的瀏覽器則相反。對于先觸發(fā) click 事件的瀏覽器,意味著會(huì)在提交發(fā)生之前禁用按鈕,結(jié)果

永遠(yuǎn)都不會(huì)提交表單。因此,最好是通過 submit 事件來禁用提交按鈕。不過,這種方式不適合表

單中不包含提交按鈕的情況;如前所述,只有在包含提交按鈕的情況下,才有可能觸發(fā)表單的 submit

事件。

除了<fieldset>之外,所有表單字段都有 type 屬性。對于<input>元素,這個(gè)值等于 HTML 特

性 type 的值。對于其他元素,這個(gè) type 屬性的值如下表所列。

說 明 HTML示例 type屬性的值

單選列表 <select>...</select> \"select-one\"

多選列表 <select multiple>...</select> \"select-multiple\"

自定義按鈕 <button>...</button> \"submit\"

自定義非提交按鈕 <button type=\"button\">...</button> \"button\"

自定義重置按鈕 <button type=\"reset\">...</buton> \"reset\"

自定義提交按鈕 <button type=\"submit\">...</buton> \"submit\"

此外,<input>和<button>元素的 type 屬性是可以動(dòng)態(tài)修改的,而<select>元素的 type 屬性

則是只讀的。

2. 共有的表單字段方法

每個(gè)表單字段都有兩個(gè)方法:focus()和 blur()。其中,focus()方法用于將瀏覽器的焦點(diǎn)設(shè)置

到表單字段,即激活表單字段,使其可以響應(yīng)鍵盤事件。例如,接收到焦點(diǎn)的文本框會(huì)顯示插入符號(hào),

隨時(shí)可以接收輸入。使用 focus()方法,可以將用戶的注意力吸引到頁面中的某個(gè)部位。例如,在頁面

加載完畢后,將焦點(diǎn)轉(zhuǎn)移到表單中的第一個(gè)字段。為此,可以偵聽頁面的 load 事件,并在該事件發(fā)生

時(shí)在表單的第一個(gè)字段上調(diào)用 focus()方法,如下面的例子所示。

EventUtil.addHandler(window, \"load\", function(event){

document.forms[0].elements[0].focus();

});

要注意的是,如果第一個(gè)表單字段是一個(gè)<input>元素,且其 type 特性的值為\"hidden\",那么

以上代碼會(huì)導(dǎo)致錯(cuò)誤。另外,如果使用 CSS 的 display 和 visibility 屬性隱藏了該字段,同樣也會(huì)

導(dǎo)致錯(cuò)誤。

HTML5 為表單字段新增了一個(gè) autofocus 屬性。在支持這個(gè)屬性的瀏覽器中,只要設(shè)置這個(gè)屬性,

不用 JavaScript 就能自動(dòng)把焦點(diǎn)移動(dòng)到相應(yīng)字段。例如:

<input type=\"text\" autofocus>

為了保證前面的代碼在設(shè)置 autofocus 的瀏覽器中正常運(yùn)行,必須先檢測是否設(shè)置了該屬性,如

果設(shè)置了,就不用再調(diào)用 focus()了。

EventUtil.addHandler(window, \"load\", function(event){

var element = document.forms[0].elements[0];

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第436頁

418 第 14 章 表單腳本

if (element.autofocus !== true){

element.focus(); console.log(\"JS focus\");

}

});

FocusExample01.htm

因?yàn)?autofocus 是一個(gè)布爾值屬性,所以在支持的瀏覽器中它的值應(yīng)該是 true。(在不支持的瀏

覽器中,它的值將是空字符串。)為此,上面的代碼只有在 autofocus 不等于 true 的情況下才會(huì)調(diào)用

focus(),從而保證向前兼容。支持 autofocus 屬性的瀏覽器有 Firefox 4+、Safari 5+、Chrome 和 Opera

9.6。

在默認(rèn)情況下,只有表單字段可以獲得焦點(diǎn)。對于其他元素而言,如果先將其

tabIndex 屬性設(shè)置為?1,然后再調(diào)用 focus()方法,也可以讓這些元素獲得焦點(diǎn)。

只有 Opera 不支持這種技術(shù)。

與 focus()方法相對的是 blur()方法,它的作用是從元素中移走焦點(diǎn)。在調(diào)用 blur()方法時(shí),

并不會(huì)把焦點(diǎn)轉(zhuǎn)移到某個(gè)特定的元素上;僅僅是將焦點(diǎn)從調(diào)用這個(gè)方法的元素上面移走而已。在早期

Web 開發(fā)中,那時(shí)候的表單字段還沒有 readonly 特性,因此就可以使用 blur()方法來創(chuàng)建只讀字段。

現(xiàn)在,雖然需要使用 blur()的場合不多了,但必要時(shí)還可以使用的。用法如下:

document.forms[0].elements[0].blur();

3. 共有的表單字段事件

除了支持鼠標(biāo)、鍵盤、更改和 HTML 事件之外,所有表單字段都支持下列 3 個(gè)事件。

? blur:當(dāng)前字段失去焦點(diǎn)時(shí)觸發(fā)。

? change:對于<input>和<textarea>元素,在它們失去焦點(diǎn)且 value 值改變時(shí)觸發(fā);對于

<select>元素,在其選項(xiàng)改變時(shí)觸發(fā)。

? focus:當(dāng)前字段獲得焦點(diǎn)時(shí)觸發(fā)。

當(dāng)用戶改變了當(dāng)前字段的焦點(diǎn),或者我們調(diào)用了 blur()或 focus()方法時(shí),都可以觸發(fā) blur 和

focus 事件。這兩個(gè)事件在所有表單字段中都是相同的。但是,change 事件在不同表單控件中觸發(fā)的

次數(shù)會(huì)有所不同。對于<input>和<textarea>元素,當(dāng)它們從獲得焦點(diǎn)到失去焦點(diǎn)且 value 值改變時(shí),

才會(huì)觸發(fā) change 事件。對于<select>元素,只要用戶選擇了不同的選項(xiàng),就會(huì)觸發(fā) change 事件;

換句話說,不失去焦點(diǎn)也會(huì)觸發(fā) change 事件。

通常,可以使用 focus 和 blur 事件來以某種方式改變用戶界面,要么是向用戶給出視覺提示,要

么是向界面中添加額外的功能(例如,為文本框顯示一個(gè)下拉選項(xiàng)菜單)。而 change 事件則經(jīng)常用于

驗(yàn)證用戶在字段中輸入的數(shù)據(jù)。例如,假設(shè)有一個(gè)文本框,我們只允許用戶輸入數(shù)值。此時(shí),可以利用

focus 事件修改文本框的背景顏色,以便更清楚地表明這個(gè)字段獲得了焦點(diǎn)??梢岳?blur 事件恢復(fù)

文本框的背景顏色,利用 change 事件在用戶輸入了非數(shù)值字符時(shí)再次修改背景顏色。下面就給出了實(shí)

現(xiàn)上述功能的代碼。

var textbox = document.forms[0].elements[0];

EventUtil.addHandler(textbox, \"focus\", function(event){

event = EventUtil.getEvent(event);

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第437頁

14.2 文本框腳本 419

14

2

3

4

5

13

6

7

8

9

10

11

12

var target = EventUtil.getTarget(event);

if (target.style.backgroundColor != \"red\"){

target.style.backgroundColor = \"yellow\";

}

});

EventUtil.addHandler(textbox, \"blur\", function(event){

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

if (/[^\\d]/.test(target.value)){

target.style.backgroundColor = \"red\";

} else {

target.style.backgroundColor = \"\";

}

});

EventUtil.addHandler(textbox, \"change\", function(event){

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

if (/[^\\d]/.test(target.value)){

target.style.backgroundColor = \"red\";

} else {

target.style.backgroundColor = \"\";

}

});

FormFieldEventsExample01.htm

在此,onfocus 事件處理程序?qū)⑽谋究虻谋尘邦伾薷臑辄S色,以清楚地表明當(dāng)前字段已經(jīng)激活。

隨后,onblur 和 onchange 事件處理程序則會(huì)在發(fā)現(xiàn)非數(shù)值字符時(shí),將文本框背景顏色修改為紅色。

為了測試用戶輸入的是不是非數(shù)值,這里針對文本框的 value 屬性使用了簡單的正則表達(dá)式。而且,

為確保無論文本框的值如何變化,驗(yàn)證規(guī)則始終如一,onblur 和 onchange 事件處理程序中使用了相

同的正則表達(dá)式。

關(guān)于 blur 和 change 事件的關(guān)系,并沒有嚴(yán)格的規(guī)定。在某些瀏覽器中,blur

事件會(huì)先于 change 事件發(fā)生;而在其他瀏覽器中,則恰好相反。為此,不能假定這

兩個(gè)事件總會(huì)以某種順序依次觸發(fā),這一點(diǎn)要特別注意。

14.2 文本框腳本

在 HTML 中,有兩種方式來表現(xiàn)文本框:一種是使用<input>元素的單行文本框,另一種是使用

<textarea>的多行文本框。這兩個(gè)控件非常相似,而且多數(shù)時(shí)候的行為也差不多。不過,它們之間仍

然存在一些重要的區(qū)別。

要表現(xiàn)文本框,必須將<input>元素的 type 特性設(shè)置為\"text\"。而通過設(shè)置 size 特性,可以指

定文本框中能夠顯示的字符數(shù)。通過 value 特性,可以設(shè)置文本框的初始值,而 maxlength 特性則用

于指定文本框可以接受的最大字符數(shù)。如果要?jiǎng)?chuàng)建一個(gè)文本框,讓它能夠顯示 25 個(gè)字符,但輸入不能

超過 50 個(gè)字符,可以使用以下代碼:

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第438頁

420 第 14 章 表單腳本

<input type=\"text\" size=\"25\" maxlength=\"50\" value=\"initial value\">

相對而言,<textarea>元素則始終會(huì)呈現(xiàn)為一個(gè)多行文本框。要指定文本框的大小,可以使用 rows

和 cols 特性。其中,rows 特性指定的是文本框的字符行數(shù),而 cols 特性指定的是文本框的字符列數(shù)

(類似于<inpu>元素的 size 特性)。與<input>元素不同,<textarea>的初始值必須要放在

<textarea>和</textarea>之間,如下面的例子所示。

<textarea rows=\"25\" cols=\"5\">initial value</textarea>

另一個(gè)與<input>的區(qū)別在于,不能在 HTML 中給<textarea>指定最大字符數(shù)。

無論這兩種文本框在標(biāo)記中有什么區(qū)別,但它們都會(huì)將用戶輸入的內(nèi)容保存在 value 屬性中。可

以通過這個(gè)屬性讀取和設(shè)置文本框的值,如下面的例子所示:

var textbox = document.forms[0].elements[\"textbox1\"];

alert(textbox.value);

textbox.value = \"Some new value\";

我們建議讀者像上面這樣使用 value 屬性讀取或設(shè)置文本框的值,不建議使用標(biāo)準(zhǔn)的 DOM 方法。

換句話說,不要使用 setAttribute()設(shè)置<input>元素的 value 特性,也不要去修改<textarea>

元素的第一個(gè)子節(jié)點(diǎn)。原因很簡單:對 value 屬性所作的修改,不一定會(huì)反映在 DOM 中。因此,在處

理文本框的值時(shí),最好不要使用 DOM 方法。

14.2.1 選擇文本

上述兩種文本框都支持 select()方法,這個(gè)方法用于選擇文本框中的所有文本。在調(diào)用 select()

方法時(shí),大多數(shù)瀏覽器(Opera 除外)都會(huì)將焦點(diǎn)設(shè)置到文本框中。這個(gè)方法不接受參數(shù),可以在任何

時(shí)候被調(diào)用。下面來看一個(gè)例子。

var textbox = document.forms[0].elements[\"textbox1\"];

textbox.select();

在文本框獲得焦點(diǎn)時(shí)選擇其所有文本,這是一種非常常見的做法,特別是在文本框包含默認(rèn)值的時(shí)

候。因?yàn)檫@樣做可以讓用戶不必一個(gè)一個(gè)地刪除文本。下面展示了實(shí)現(xiàn)這一操作的代碼。

EventUtil.addHandler(textbox, \"focus\", function(event){

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

target.select();

});

TextboxSelectExample01.htm

將上面的代碼應(yīng)用到文本框之后,只要文本框獲得焦點(diǎn),就會(huì)選擇其中所有的文本。這種技術(shù)能夠

較大幅度地提升表單的易用性。

1. 選擇(select)事件

與 select()方法對應(yīng)的,是一個(gè) select 事件。在選擇了文本框中的文本時(shí),就會(huì)觸發(fā) select

事件。不過,到底什么時(shí)候觸發(fā) select 事件,還會(huì)因?yàn)g覽器而異。在 IE9+、Opera、Firefox、Chrome

和 Safari 中,只有用戶選擇了文本(而且要釋放鼠標(biāo)),才會(huì)觸發(fā) select 事件。而在 IE8 及更早版本中,

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第439頁

14.2 文本框腳本 421

14

2

3

4

5

13

6

7

8

9

10

11

12

只要用戶選擇了一個(gè)字母(不必釋放鼠標(biāo)),就會(huì)觸發(fā) select 事件。另外,在調(diào)用 select()方法時(shí)也

會(huì)觸發(fā) select 事件。下面是一個(gè)簡單的例子。

var textbox = document.forms[0].elements[\"textbox1\"];

EventUtil.addHandler(textbox, \"select\", function(event){

var alert(\"Text selected\" + textbox.value);

});

SelectEventExample01.htm

2. 取得選擇的文本

雖然通過select 事件我們可以知道用戶什么時(shí)候選擇了文本,但仍然不知道用戶選擇了什么文本。

HTML5 通過一些擴(kuò)展方案解決了這個(gè)問題,以便更順利地取得選擇的文本。該規(guī)范采取的辦法是添加

兩個(gè)屬性:selectionStart 和 selectionEnd。這兩個(gè)屬性中保存的是基于 0 的數(shù)值,表示所選擇

文本的范圍(即文本選區(qū)開頭和結(jié)尾的偏移量)。因此,要取得用戶在文本框中選擇的文本,可以使用

如下代碼。

function getSelectedText(textbox){

return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd);

}

因 為 substring() 方法基于字符串的偏移量執(zhí)行操作,所以將 selectionStart 和

selectionEnd 直接傳給它就可以取得選中的文本。

IE9+、Firefox、Safari、Chrome 和 Opera 都支持這兩個(gè)屬性。IE8 及之前版本不支持這兩個(gè)屬性,

而是提供了另一種方案。

IE8 及更早的版本中有一個(gè) document.selection 對象,其中保存著用戶在整個(gè)文檔范圍內(nèi)選擇

的文本信息;也就是說,無法確定用戶選擇的是頁面中哪個(gè)部位的文本。不過,在與 select 事件一起

使用的時(shí)候,可以假定是用戶選擇了文本框中的文本,因而觸發(fā)了該事件。要取得選擇的文本,首先必

須創(chuàng)建一個(gè)范圍(第 12 章討論過),然后再將文本從其中提取出來,如下面的例子所示。

function getSelectedText(textbox){

if (typeof textbox.selectionStart == \"number\"){

return textbox.value.substring(textbox.selectionStart,

textbox.selectionEnd);

} else if (document.selection){

return document.selection.createRange().text;

}

}

TextboxGetSelectedTextExample01.htm

這里修改了前面的函數(shù),包括了在 IE 中取得選擇文本的代碼。注意,調(diào)用 document.selection

時(shí),不需要考慮 textbox 參數(shù)。

3. 選擇部分文本

HTML5 也為選擇文本框中的部分文本提供了解決方案,即最早由 Firefox 引入的

setSelectionRange()方法。現(xiàn)在除select()方法之外,所有文本框都有一個(gè)setSelectionRange()

方法。這個(gè)方法接收兩個(gè)參數(shù):要選擇的第一個(gè)字符的索引和要選擇的最后一個(gè)字符之后的字符的索引

(類似于 substring()方法的兩個(gè)參數(shù))。來看一個(gè)例子。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第440頁

422 第 14 章 表單腳本

textbox.value = \"Hello world!\"

//選擇所有文本

textbox.setSelectionRange(0, textbox.value.length); //\"Hello world!\"

//選擇前 3 個(gè)字符

textbox.setSelectionRange(0, 3); //\"Hel\"

//選擇第 4 到第 6 個(gè)字符

textbox.setSelectionRange(4, 7); //\"o w\"

要看到選擇的文本,必須在調(diào)用 setSelectionRange()之前或之后立即將焦點(diǎn)設(shè)置到文本框。

IE9、Firefox、Safari、Chrome 和 Opera 支持這種方案。

IE8 及更早版本支持使用范圍(第 12 章討論過)選擇部分文本。要選擇文本框中的部分文本,必須

首先使用 IE 在所有文本框上提供的 createTextRange()方法創(chuàng)建一個(gè)范圍,并將其放在恰當(dāng)?shù)奈恢?/p>

上。然后,再使用 moveStart()和 moveEnd()這兩個(gè)范圍方法將范圍移動(dòng)到位。不過,在調(diào)用這兩個(gè)

方法以前,還必須使用 collapse()將范圍折疊到文本框的開始位置。此時(shí),moveStart()將范圍的起

點(diǎn)和終點(diǎn)移動(dòng)到了相同的位置,只要再給 moveEnd()傳入要選擇的字符總數(shù)即可。最后一步,就是使

用范圍的 select()方法選擇文本,如下面的例子所示。

textbox.value = \"Hello world!\";

var range = textbox.createTextRange();

//選擇所有文本

range.collapse(true);

range.moveStart(\"character\", 0);

range.moveEnd(\"character\", textbox.value.length); //\"Hello world!\"

range.select();

//選擇前 3 個(gè)字符

range.collapse(true);

range.moveStart(\"character\", 0);

range.moveEnd(\"character\", 3);

range.select(); //\"Hel\"

//選擇第 4 到第 6 個(gè)字符

range.collapse(true);

range.moveStart(\"character\", 4);

range.moveEnd(\"character\", 3);

range.select(); //\"o w\"

與在其他瀏覽器中一樣,要想在文本框中看到文本被選擇的效果,必須讓文本框獲得焦點(diǎn)。

為了實(shí)現(xiàn)跨瀏覽器編程,可以將上述兩種方案組合起來,如下面的例子所示。

function selectText(textbox, startIndex, stopIndex){

if (textbox.setSelectionRange){

textbox.setSelectionRange(startIndex, stopIndex);

} else if (textbox.createTextRange){

var range = textbox.createTextRange();

range.collapse(true);

range.moveStart(\"character\", startIndex);

range.moveEnd(\"character\", stopIndex - startIndex);

range.select();

}

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第441頁

14.2 文本框腳本 423

14

2

3

4

5

13

6

7

8

9

10

11

12

textbox.focus();

}

TextboxPartialSelectionExample01.htm

這個(gè) selectText()函數(shù)接收三個(gè)參數(shù):要操作的文本框、要選擇文本中第一個(gè)字符的索引和要選

擇文本中最后一個(gè)字符之后的索引。首先,函數(shù)測試了文本框是否包含 setSelectionRange()方法。

如果有,則使用該方法。否則,檢測文本框是否支持 createTextRange()方法。如果支持,則通過創(chuàng)

建范圍來實(shí)現(xiàn)選擇。最后一步,就是為文本框設(shè)置焦點(diǎn),以便用戶看到文本框中選擇的文本。可以像下

面這樣使用 selectText()方法。

textbox.value = \"Hello world!\"

//選擇所有文本

selectText(textbox, 0, textbox.value.length); //\"Hello world!\"

//選擇前 3 個(gè)字符

selectText(textbox, 0, 3); //\"Hel\"

//選擇第 4 到第 6 個(gè)字符

selectText(textbox, 4, 7); //\"o w\"

選擇部分文本的技術(shù)在實(shí)現(xiàn)高級(jí)文本輸入框時(shí)很有用,例如提供自動(dòng)完成建議的文本框就可以使用

這種技術(shù)。

14.2.2 過濾輸入

我們經(jīng)常會(huì)要求用戶在文本框中輸入特定的數(shù)據(jù),或者輸入特定格式的數(shù)據(jù)。例如,必須包含某些

字符,或者必須匹配某種模式。由于文本框在默認(rèn)情況下沒有提供多少驗(yàn)證數(shù)據(jù)的手段,因此必須使用

JavaScript 來完成此類過濾輸入的操作。而綜合運(yùn)用事件和 DOM 手段,就可以將普通的文本框轉(zhuǎn)換成能

夠理解用戶輸入數(shù)據(jù)的功能型控件。

1. 屏蔽字符

有時(shí)候,我們需要用戶輸入的文本中包含或不包含某些字符。例如,電話號(hào)碼中不能包含非數(shù)值字

符。如前所述,響應(yīng)向文本框中插入字符操作的是 keypress 事件。因此,可以通過阻止這個(gè)事件的默

認(rèn)行為來屏蔽此類字符。在極端的情況下,可以通過下列代碼屏蔽所有按鍵操作。

EventUtil.addHandler(textbox, \"keypress\", function(event){

event = EventUtil.getEvent(event);

EventUtil.preventDefault(event);

});

運(yùn)行以上代碼后,由于所有按鍵操作都將被屏蔽,結(jié)果會(huì)導(dǎo)致文本框變成只讀的。如果只想屏蔽特

定的字符,則需要檢測 keypress 事件對應(yīng)的字符編碼,然后再?zèng)Q定如何響應(yīng)。例如,下列代碼只允許

用戶輸入數(shù)值。

EventUtil.addHandler(textbox, \"keypress\", function(event){

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

var charCode = EventUtil.getCharCode(event);

if (!/\\d/.test(String.fromCharCode(charCode))){

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第442頁

424 第 14 章 表單腳本

EventUtil.preventDefault(event);

}

});

在這個(gè)例子中,我們使用 EventUtil.getCharCode()實(shí)現(xiàn)了跨瀏覽器取得字符編碼。然后,使

用 String.fromCharCode()將字符編碼轉(zhuǎn)換成字符串,再使用正則表達(dá)式 /\\d/ 來測試該字符串,從

而確定用戶輸入的是不是數(shù)值。如果測試失敗,那么就使用 EventUtil.preventDefault()屏蔽按鍵

事件。結(jié)果,文本框就會(huì)忽略所有輸入的非數(shù)值。

雖然理論上只應(yīng)該在用戶按下字符鍵時(shí)才觸發(fā) keypress 事件,但有些瀏覽器也會(huì)對其他鍵觸發(fā)此

事件。Firefox 和 Safari(3.1 版本以前)會(huì)對向上鍵、向下鍵、退格鍵和刪除鍵觸發(fā) keypress 事件;

Safari 3.1 及更新版本則不會(huì)對這些鍵觸發(fā) keypress 事件。這意味著,僅考慮到屏蔽不是數(shù)值的字符還

不夠,還要避免屏蔽這些極為常用和必要的鍵。所幸的是,要檢測這些鍵并不困難。在 Firefox 中,所

有由非字符鍵觸發(fā)的 keypress 事件對應(yīng)的字符編碼為 0,而在 Safari 3 以前的版本中,對應(yīng)的字符編

碼全部為 8。為了讓代碼更通用,只要不屏蔽那些字符編碼小于 10 的鍵即可。故而,可以將上面的函數(shù)

重寫成如下所示。

EventUtil.addHandler(textbox, \"keypress\", function(event){

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

var charCode = EventUtil.getCharCode(event);

if (!/\\d/.test(String.fromCharCode(charCode)) && charCode > 9){

EventUtil.preventDefault(event);

}

});

這樣,我們的事件處理程序就可以適用所有瀏覽器了,即可以屏蔽非數(shù)值字符,但不屏蔽那些也會(huì)

觸發(fā) keypress 事件的基本按鍵。

除此之外,還有一個(gè)問題需要處理:復(fù)制、粘貼及其他操作還要用到 Ctrl 鍵。在除 IE 之外的所有

瀏覽器中,前面的代碼也會(huì)屏蔽 Ctrl+C、Ctrl+V,以及其他使用 Ctrl 的組合鍵。因此,最后還要添加一

個(gè)檢測條件,以確保用戶沒有按下 Ctrl 鍵,如下面的例子所示。

EventUtil.addHandler(textbox, \"keypress\", function(event){

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

var charCode = EventUtil.getCharCode(event);

if (!/\\d/.test(String.fromCharCode(charCode)) && charCode > 9 &&

!event.ctrlKey){

EventUtil.preventDefault(event);

}

});

TextboxInputFilteringExample01.htm

經(jīng)過最后一點(diǎn)修改,就可以確保文本框的行為完全正常了。在這個(gè)例子的基礎(chǔ)上加以修改和調(diào)整,

就可以將同樣的技術(shù)運(yùn)用于放過和屏蔽任何輸入文本框的字符。

2. 操作剪貼板

IE 是第一個(gè)支持與剪貼板相關(guān)事件,以及通過 JavaScript 訪問剪貼板數(shù)據(jù)的瀏覽器。IE 的實(shí)現(xiàn)成為

了事實(shí)上的標(biāo)準(zhǔn),不僅 Safari 2、Chrome 和 Firefox 3 也都支持類似的事件和剪貼板訪問(Opera 不支持

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第443頁

14.2 文本框腳本 425

14

2

3

4

5

13

6

7

8

9

10

11

12

通過 JavaScript 訪問剪貼板),HTML 5 后來也把剪貼板事件納入了規(guī)范。下列就是 6 個(gè)剪貼板事件。

? beforecopy:在發(fā)生復(fù)制操作前觸發(fā)。

? copy:在發(fā)生復(fù)制操作時(shí)觸發(fā)。

? beforecut:在發(fā)生剪切操作前觸發(fā)。

? cut:在發(fā)生剪切操作時(shí)觸發(fā)。

? beforepaste:在發(fā)生粘貼操作前觸發(fā)。

? paste:在發(fā)生粘貼操作時(shí)觸發(fā)。

由于沒有針對剪貼板操作的標(biāo)準(zhǔn),這些事件及相關(guān)對象會(huì)因?yàn)g覽器而異。在 Safari、Chrome 和 Firefox

中,beforecopy、beforecut 和 beforepaste 事件只會(huì)在顯示針對文本框的上下文菜單(預(yù)期將發(fā)

生剪貼板事件)的情況下觸發(fā)。但是,IE 則會(huì)在觸發(fā) copy、cut 和 paste 事件之前先行觸發(fā)這些事件。

至于 copy、cut 和 paste 事件,只要是在上下文菜單中選擇了相應(yīng)選項(xiàng),或者使用了相應(yīng)的鍵盤組合

鍵,所有瀏覽器都會(huì)觸發(fā)它們。

在實(shí)際的事件發(fā)生之前,通過 beforecopy、beforecut 和 beforepaste 事件可以在向剪貼板發(fā)

送數(shù)據(jù),或者從剪貼板取得數(shù)據(jù)之前修改數(shù)據(jù)。不過,取消這些事件并不會(huì)取消對剪貼板的操作——只

有取消 copy、cut 和 paste 事件,才能阻止相應(yīng)操作發(fā)生。

要訪問剪貼板中的數(shù)據(jù),可以使用 clipboardData 對象:在 IE 中,這個(gè)對象是 window 對象的

屬性;而在 Firefox 4+、Safari 和 Chrome 中,這個(gè)對象是相應(yīng) event 對象的屬性。但是,在 Firefox、

Safari 和 Chorme 中,只有在處理剪貼板事件期間 clipboardData 對象才有效,這是為了防止對剪貼板

的未授權(quán)訪問;在 IE 中,則可以隨時(shí)訪問 clipboardData 對象。為了確??鐬g覽器兼容性,最好只

在發(fā)生剪貼板事件期間使用這個(gè)對象。

這個(gè) clipboardData 對象有三個(gè)方法:getData()、setData()和 clearData()。其中,getData()

用于從剪貼板中取得數(shù)據(jù),它接受一個(gè)參數(shù),即要取得的數(shù)據(jù)的格式。在 IE 中,有兩種數(shù)據(jù)格式:\"text\"

和\"URL\"。在 Firefox、Safari 和 Chrome 中,這個(gè)參數(shù)是一種 MIME 類型;不過,可以用\"text\"代表

\"text/plain\"。

類似地,setData()方法的第一個(gè)參數(shù)也是數(shù)據(jù)類型,第二個(gè)參數(shù)是要放在剪貼板中的文本。對于

第一個(gè)參數(shù),IE 照樣支持\"text\"和\"URL\",而 Safari 和 Chrome 仍然只支持 MIME 類型。但是,與

getData()方法不同的是,Safari 和 Chrome 的 setData()方法不能識(shí)別\"text\"類型。這兩個(gè)瀏覽器在

成功將文本放到剪貼板中后,都會(huì)返回 true;否則,返回 false。為了彌合這些差異,我們可以向

EventUtil 中再添加下列方法。

var EventUtil = {

//省略的代碼

getClipboardText: function(event){

var clipboardData = (event.clipboardData || window.clipboardData);

return clipboardData.getData(\"text\");

},

//省略的代碼

setClipboardText: function(event, value){

if (event.clipboardData){

return event.clipboardData.setData(\"text/plain\", value);

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第444頁

426 第 14 章 表單腳本

} else if (window.clipboardData){

return window.clipboardData.setData(\"text\", value);

}

},

//省略的代碼

};

EventUtil.js

這里的 getClipboardText()方法相對簡單;它只要確定 clipboardData 對象的位置,然后再

以\"text\"類型調(diào)用 getData()方法即可。相應(yīng)地,setClipboardText()方法則要稍微復(fù)雜一些。在

取得 clipboardData 對象之后,需要根據(jù)不同的瀏覽器實(shí)現(xiàn)為 setData()傳入不同的類型(對于 Safari

和 Chrome,是\"text/plain\";對于 IE,是\"text\")。

在需要確保粘貼到文本框中的文本中包含某些字符,或者符合某種格式要求時(shí),能夠訪問剪貼板是非

常有用的。例如,如果一個(gè)文本框只接受數(shù)值,那么就必須檢測粘貼過來的值,以確保有效。在 paste

事件中,可以確定剪貼板中的值是否有效,如果無效,就可以像下面示例中那樣,取消默認(rèn)的行為。

EventUtil.addHandler(textbox, \"paste\", function(event){

event = EventUtil.getEvent(event);

var text = EventUtil.getClipboardText(event);

if (!/^\\d*$/.test(text)){

EventUtil.preventDefault(event);

}

});

TextboxClipboardExample01.htm

在這里,onpaste 事件處理程序可以確保只有數(shù)值才會(huì)被粘貼到文本框中。如果剪貼板的值與正

則表達(dá)式不匹配,則會(huì)取消粘貼操作。Firefox、Safari 和 Chrome 只允許在 onpaste 事件處理程序中訪

問 getData()方法。

由于并非所有瀏覽器都支持訪問剪貼板,所以更簡單的做法是屏蔽一或多個(gè)剪貼板操作。在支持

copy、cut 和 paste 事件的瀏覽器中(IE、Safari、Chrome 和 Firefox 3 及更高版本),很容易阻止這些

事件的默認(rèn)行為。在 Opera 中,則需要阻止那些會(huì)觸發(fā)這些事件的按鍵操作,同時(shí)還要阻止在文本框中

顯示上下文菜單。

14.2.3 自動(dòng)切換焦點(diǎn)

使用 JavaScript 可以從多個(gè)方面增強(qiáng)表單字段的易用性。其中,最常見的一種方式就是在用戶填寫

完當(dāng)前字段時(shí),自動(dòng)將焦點(diǎn)切換到下一個(gè)字段。通常,在自動(dòng)切換焦點(diǎn)之前,必須知道用戶已經(jīng)輸入了

既定長度的數(shù)據(jù)(例如電話號(hào)碼)。例如,美國的電話號(hào)碼通常會(huì)分為三部分:區(qū)號(hào)、局號(hào)和另外 4 位

數(shù)字。為取得完整的電話號(hào)碼,很多網(wǎng)頁中都會(huì)提供下列 3 個(gè)文本框:

<input type=\"text\" name=\"tel1\" id=\"txtTel1\" maxlength=\"3\">

<input type=\"text\" name=\"tel2\" id=\"txtTel2\" maxlength=\"3\">

<input type=\"text\" name=\"tel3\" id=\"txtTel3\" maxlength=\"4\">

TextboxTabForwardExample01.htm

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第445頁

14.2 文本框腳本 427

14

2

3

4

5

13

6

7

8

9

10

11

12

為增強(qiáng)易用性,同時(shí)加快數(shù)據(jù)輸入,可以在前一個(gè)文本框中的字符達(dá)到最大數(shù)量后,自動(dòng)將焦點(diǎn)切

換到下一個(gè)文本框。換句話說,用戶在第一個(gè)文本框中輸入了 3 個(gè)數(shù)字之后,焦點(diǎn)就會(huì)切換到第二個(gè)文

本框,再輸入 3 個(gè)數(shù)字,焦點(diǎn)又會(huì)切換到第三個(gè)文本框。這種“自動(dòng)切換焦點(diǎn)”的功能,可以通過下列

代碼實(shí)現(xiàn):

(function(){

function tabForward(event){

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

if (target.value.length == target.maxLength){

var form = target.form;

for (var i=0, len=form.elements.length; i < len; i++) {

if (form.elements[i] == target) {

if (form.elements[i+1]){

form.elements[i+1].focus();

}

return;

}

}

}

}

var textbox1 = document.getElementById(\"txtTel1\");

var textbox2 = document.getElementById(\"txtTel2\");

var textbox3 = document.getElementById(\"txtTel3\");

EventUtil.addHandler(textbox1, \"keyup\", tabForward);

EventUtil.addHandler(textbox2, \"keyup\", tabForward);

EventUtil.addHandler(textbox3, \"keyup\", tabForward);

})();

TextboxTabForwardExample01.htm

開始的 tabForward()函數(shù)是實(shí)現(xiàn)“自動(dòng)切換焦點(diǎn)”的關(guān)鍵所在。這個(gè)函數(shù)通過比較用戶輸入的值

與文本框的 maxlength 特性,可以確定是否已經(jīng)達(dá)到最大長度。如果這兩個(gè)值相等(因?yàn)闉g覽器最終

會(huì)強(qiáng)制它們相等,因此用戶絕不會(huì)多輸入字符),則需要查找表單字段集合,直至找到下一個(gè)文本框。

找到下一個(gè)文本框之后,則將焦點(diǎn)切換到該文本框。然后,我們把這個(gè)函數(shù)指定為每個(gè)文本框的 onkeyup

事件處理程序。由于 keyup 事件會(huì)在用戶輸入了新字符之后觸發(fā),所以此時(shí)是檢測文本框中內(nèi)容長度

的最佳時(shí)機(jī)。這樣一來,用戶在填寫這個(gè)簡單的表單時(shí),就不必再通過按制表鍵切換表單字段和提交表

單了。

不過請記住,這些代碼只適用于前面給出的標(biāo)記,而且沒有考慮隱藏字段。

14.2.4 HTML5 約束驗(yàn)證API

為了在將表單提交到服務(wù)器之前驗(yàn)證數(shù)據(jù),HTML5 新增了一些功能。有了這些功能,即便 JavaScript

被禁用或者由于種種原因未能加載,也可以確?;镜尿?yàn)證。換句話說,瀏覽器自己會(huì)根據(jù)標(biāo)記中的規(guī)

則執(zhí)行驗(yàn)證,然后自己顯示適當(dāng)?shù)腻e(cuò)誤消息(完全不用 JavaScript 插手)。當(dāng)然,這個(gè)功能只有在支持

HTML5 這部分內(nèi)容的瀏覽器中才有效,這些瀏覽器有 Firefox 4+、Safari 5+、Chrome 和 Opera 10+。

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第446頁

428 第 14 章 表單腳本

只有在某些情況下表單字段才能進(jìn)行自動(dòng)驗(yàn)證。具體來說,就是要在 HTML 標(biāo)記中為特定的字段

指定一些約束,然后瀏覽器才會(huì)自動(dòng)執(zhí)行表單驗(yàn)證。

1. 必填字段

第一種情況是在表單字段中指定了 required 屬性,如下面的例子所示:

<input type=\"text\" name=\"username\" required>

任何標(biāo)注有 required 的字段,在提交表單時(shí)都不能空著。這個(gè)屬性適用于<input>、<textarea>

和<select>字段(Opera 11 及之前版本還不支持<select>的 required 屬性)。在 JavaScript 中,通過

對應(yīng)的 required 屬性,可以檢查某個(gè)表單字段是否為必填字段。

var isUsernameRequired = document.forms[0].elements[\"username\"].required;

另外,使用下面這行代碼可以測試瀏覽器是否支持 required 屬性。

var isRequiredSupported = \"required\" in document.createElement(\"input\");

以上代碼通過特性檢測來確定新創(chuàng)建的<input>元素中是否存在 required 屬性。

對于空著的必填字段,不同瀏覽器有不同的處理方式。Firefox 4 和 Opera 11 會(huì)阻止表單提交并在相

應(yīng)字段下方彈出幫助框,而 Safari(5 之前)和 Chrome(9 之前)則什么也不做,而且也不阻止表單提

交。

2. 其他輸入類型

HTML5 為<input>元素的 type 屬性又增加了幾個(gè)值。這些新的類型不僅能反映數(shù)據(jù)類型的信息,

而且還能提供一些默認(rèn)的驗(yàn)證功能。其中,\"email\"和\"url\"是兩個(gè)得到支持最多的類型,各瀏覽器也

都為它們增加了定制的驗(yàn)證機(jī)制。例如:

<input type=\"email\" name =\"email\">

<input type=\"url\" name=\"homepage\">

顧名思義,\"email\"類型要求輸入的文本必須符合電子郵件地址的模式,而\"url\"類型要求輸入的

文本必須符合 URL 的模式。不過,本節(jié)前面提到的瀏覽器在恰當(dāng)?shù)仄ヅ淠J椒矫娑即嬖趩栴}。最明顯

的是\"-@-\"會(huì)被當(dāng)成一個(gè)有效的電子郵件地址。瀏覽器開發(fā)商還在解決這些問題。

要檢測瀏覽器是否支持這些新類型,可以在 JavaScript 創(chuàng)建一個(gè)<input>元素,然后將 type 屬性

設(shè)置為\"email\"或\"url\",最后再檢測這個(gè)屬性的值。不支持它們的舊版本瀏覽器會(huì)自動(dòng)將未知的值設(shè)

置為\"text\",而支持的瀏覽器則會(huì)返回正確的值。例如:

var input = document.createElement(\"input\");

input.type = \"email\";

var isEmailSupported = (input.type == \"email\");

要注意的是,如果不給<input>元素設(shè)置 required 屬性,那么空文本框也會(huì)驗(yàn)證通過。另一方面,

設(shè)置特定的輸入類型并不能阻止用戶輸入無效的值,只是應(yīng)用某些默認(rèn)的驗(yàn)證而已。

3. 數(shù)值范圍

除了\"email\"和\"url\",HTML5 還定義了另外幾個(gè)輸入元素。這幾個(gè)元素都要求填寫某種基于數(shù)

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第447頁

14.2 文本框腳本 429

14

2

3

4

5

13

6

7

8

9

10

11

12

字的值:\"number\"、\"range\"、\"datetime\"、\"datetime-local\"、\"date\"、\"month\"、\"week\",

還有\"time\"。瀏覽器對這幾個(gè)類型的支持情況并不好,因此如果真想選用的話,要特別小心。目前,

瀏覽器開發(fā)商主要關(guān)注更好的跨平臺(tái)兼容性以及更多的邏輯功能。因此,本節(jié)介紹的內(nèi)容某種程度上有

些超前,不一定馬上就能在實(shí)際開發(fā)中使用。

對所有這些數(shù)值類型的輸入元素,可以指定 min 屬性(最小的可能值)、max 屬性(最大的可能值)

和 step 屬性(從 min 到 max 的兩個(gè)刻度間的差值)。例如,想讓用戶只能輸入 0 到 100 的值,而且這

個(gè)值必須是 5 的倍數(shù),可以這樣寫代碼:

<input type=\"number\" min=\"0\" max=\"100\" step=\"5\" name=\"count\">

在不同的瀏覽器中,可能會(huì)也可能不會(huì)看到能夠自動(dòng)遞增和遞減的數(shù)值調(diào)節(jié)按鈕(向上和向下按

鈕)。

以上這些屬性在 JavaScript 中都能通過對應(yīng)的元素訪問(或修改)。此外,還有兩個(gè)方法:stepUp()

和 stepDown(),都接收一個(gè)可選的參數(shù):要在當(dāng)前值基礎(chǔ)上加上或減去的數(shù)值。(默認(rèn)是加或減 1。)

這兩個(gè)方法還沒有得到任何瀏覽器支持,但下面的例子演示了它們的用法。

input.stepUp(); //加 1

input.stepUp(5); //加 5

input.stepDown(); //減 1

input.stepDown(10); //減 10

4. 輸入模式

HTML5 為文本字段新增了 pattern 屬性。這個(gè)屬性的值是一個(gè)正則表達(dá)式,用于匹配文本框中的

值。例如,如果只想允許在文本字段中輸入數(shù)值,可以像下面的代碼一樣應(yīng)用約束:

<input type=\"text\" pattern=\"\\d+\" name=\"count\">

注意,模式的開頭和末尾不用加^和$符號(hào)(假定已經(jīng)有了)。這兩個(gè)符號(hào)表示輸入的值必須從頭到

尾都與模式匹配。

與其他輸入類型相似,指定 pattern 也不能阻止用戶輸入無效的文本。這個(gè)模式應(yīng)用給值,瀏覽

器來判斷值是有效,還是無效。在 JavaScript 中可以通過 pattern 屬性訪問模式。

var pattern = document.forms[0].elements[\"count\"].pattern;

使用以下代碼可以檢測瀏覽器是否支持 pattern 屬性。

var isPatternSupported = \"pattern\" in document.createElement(\"input\");

5. 檢測有效性

使用 checkValidity()方法可以檢測表單中的某個(gè)字段是否有效。所有表單字段都有個(gè)方法,如

果字段的值有效,這個(gè)方法返回 true,否則返回 false。字段的值是否有效的判斷依據(jù)是本節(jié)前面介

紹過的那些約束。換句話說,必填字段中如果沒有值就是無效的,而字段中的值與 pattern 屬性不匹

配也是無效的。例如:

if (document.forms[0].elements[0].checkValidity()){

//字段有效,繼續(xù)

} else {

//字段無效

}

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第448頁

430 第 14 章 表單腳本

要檢測整個(gè)表單是否有效,可以在表單自身調(diào)用 checkValidity()方法。如果所有表單字段都有

效,這個(gè)方法返回 true;即使有一個(gè)字段無效,這個(gè)方法也會(huì)返回 false。

if(document.forms[0].checkValidity()){

//表單有效,繼續(xù)

} else {

//表單無效

}

與 checkValidity()方法簡單地告訴你字段是否有效相比,validity 屬性則會(huì)告訴你為什么字

段有效或無效。這個(gè)對象中包含一系列屬性,每個(gè)屬性會(huì)返回一個(gè)布爾值。

? customError :如果設(shè)置了setCustomValidity(),則為true,否則返回false。

? patternMismatch:如果值與指定的pattern 屬性不匹配,返回true。

? rangeOverflow:如果值比max 值大,返回true。

? rangeUnderflow:如果值比min 值小,返回true。

? stepMisMatch:如果min 和max 之間的步長值不合理,返回true。

? tooLong:如果值的長度超過了maxlength 屬性指定的長度,返回true。有的瀏覽器(如Firefox 4)

會(huì)自動(dòng)約束字符數(shù)量,因此這個(gè)值可能永遠(yuǎn)都返回false。

? typeMismatch:如果值不是\"mail\"或\"url\"要求的格式,返回true。

? valid:如果這里的其他屬性都是false,返回true。checkValidity()也要求相同的值。

? valueMissing:如果標(biāo)注為required 的字段中沒有值,返回true。

因此,要想得到更具體的信息,就應(yīng)該使用 validity 屬性來檢測表單的有效性。下面是一個(gè)例子。

if (input.validity && !input.validity.valid){

if (input.validity.valueMissing){

alert(\"Please specify a value.\")

} else if (input.validity.typeMismatch){

alert(\"Please enter an email address.\");

} else {

alert(\"Value is invalid.\");

}

}

6. 禁用驗(yàn)證

通過設(shè)置 novalidate 屬性,可以告訴表單不進(jìn)行驗(yàn)證。

<form method=\"post\" action=\"signup.php\" novalidate>

<!--這里插入表單元素-->

</form>

在 JavaScript 中使用 noValidate 屬性可以取得或設(shè)置這個(gè)值,如果這個(gè)屬性存在,值為 true,

如果不存在,值為 false。

document.forms[0].noValidate = true; //禁用驗(yàn)證

如果一個(gè)表單中有多個(gè)提交按鈕,為了指定點(diǎn)擊某個(gè)提交按鈕不必驗(yàn)證表單,可以在相應(yīng)的按鈕上

添加 formnovalidate 屬性。

<form method=\"post\" action=\"foo.php\">

<!--這里插入表單元素-->

<input type=\"submit\" value=\"Regular Submit\">

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第449頁

14.3 選擇框腳本 431

14

2

3

4

5

13

6

7

8

9

10

11

12

<input type=\"submit\" formnovalidate name=\"btnNoValidate\"

value=\"Non-validating Submit\">

</form>

在這個(gè)例子中,點(diǎn)擊第一個(gè)提交按鈕會(huì)像往常一樣驗(yàn)證表單,而點(diǎn)擊第二個(gè)按鈕則會(huì)不經(jīng)過驗(yàn)證而

提交表單。使用 JavaScript 也可以設(shè)置這個(gè)屬性。

//禁用驗(yàn)證

document.forms[0].elements[\"btnNoValidate\"].formNoValidate = true;

14.3 選擇框腳本

選擇框是通過<select>和<option>元素創(chuàng)建的。為了方便與這個(gè)控件交互,除了所有表單字段共

有的屬性和方法外,HTMLSelectElement 類型還提供了下列屬性和方法。

? add(newOption, relOption):向控件中插入新<option>元素,其位置在相關(guān)項(xiàng)(relOption)

之前。

? multiple:布爾值,表示是否允許多項(xiàng)選擇;等價(jià)于 HTML 中的 multiple 特性。

? options:控件中所有<option>元素的 HTMLCollection。

? remove(index):移除給定位置的選項(xiàng)。

? selectedIndex:基于 0 的選中項(xiàng)的索引,如果沒有選中項(xiàng),則值為-1。對于支持多選的控件,

只保存選中項(xiàng)中第一項(xiàng)的索引。

? size:選擇框中可見的行數(shù);等價(jià)于 HTML 中的 size 特性。

選擇框的 type 屬性不是\"select-one\",就是\"select-multiple\",這取決于 HTML 代碼中有

沒有 multiple 特性。選擇框的 value 屬性由當(dāng)前選中項(xiàng)決定,相應(yīng)規(guī)則如下。

? 如果沒有選中的項(xiàng),則選擇框的 value 屬性保存空字符串。

? 如果有一個(gè)選中項(xiàng),而且該項(xiàng)的 value 特性已經(jīng)在 HTML 中指定,則選擇框的 value 屬性等

于選中項(xiàng)的 value 特性。即使 value 特性的值是空字符串,也同樣遵循此條規(guī)則。

? 如果有一個(gè)選中項(xiàng),但該項(xiàng)的 value 特性在 HTML 中未指定,則選擇框的 value 屬性等于該

項(xiàng)的文本。

? 如果有多個(gè)選中項(xiàng),則選擇框的 value 屬性將依據(jù)前兩條規(guī)則取得第一個(gè)選中項(xiàng)的值。

以下面的選擇框?yàn)槔?/p>

<select name=\"location\" id=\"selLocation\">

<option value=\"Sunnyvale, CA\">Sunnyvale</option>

<option value=\"Los Angeles, CA\">Los Angeles</option>

<option value=\"Mountain View, CA\">Mountain View</option>

<option value=\"\">China</option>

<option>Australia</option>

</select>

如果用戶選擇了其中第一項(xiàng),則選擇框的值就是\"Sunnyvale, CA\"。如果文本為\"China\"的選項(xiàng)

被選中,則選擇框的值就是一個(gè)空字符串,因?yàn)槠?value 特性是空的。如果選擇了最后一項(xiàng),那么由

于<option>中沒有指定 value 特性,則選擇框的值就是\"Australia\"。

在 DOM 中,每個(gè)<option>元素都有一個(gè) HTMLOptionElement 對象表示。為便于訪問數(shù)據(jù),

HTMLOptionElement 對象添加了下列屬性:

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

第450頁

432 第 14 章 表單腳本

? index:當(dāng)前選項(xiàng)在 options 集合中的索引。

? label:當(dāng)前選項(xiàng)的標(biāo)簽;等價(jià)于 HTML 中的 label 特性。

? selected:布爾值,表示當(dāng)前選項(xiàng)是否被選中。將這個(gè)屬性設(shè)置為 true 可以選中當(dāng)前選項(xiàng)。

? text:選項(xiàng)的文本。

? value:選項(xiàng)的值(等價(jià)于 HTML 中的 value 特性)。

其中大部分屬性的目的,都是為了方便對選項(xiàng)數(shù)據(jù)的訪問。雖然也可以使用常規(guī)的 DOM 功能來訪

問這些信息,但效率是比較低的,如下面的例子所示:

var selectbox = document.forms[0].elements[\"location\"];

//不推薦

var text = selectbox.options[0].firstChild.nodeValue; //選項(xiàng)的文本

var value = selectbox.options[0].getAttribute(\"value\"); //選項(xiàng)的值

以上代碼使用標(biāo)準(zhǔn) DOM 方法,取得了選擇框中第一項(xiàng)的文本和值。可以與下面使用選項(xiàng)屬性的代

碼作一比較:

var selectbox = document.forms[0]. elements[\"location\"];

//推薦

var text = selectbox.options[0].text; //選項(xiàng)的文本

var value = selectbox.options[0].value; //選項(xiàng)的值

在操作選項(xiàng)時(shí),我們建議最好是使用特定于選項(xiàng)的屬性,因?yàn)樗袨g覽器都支持這些屬性。在將表

單控件作為 DOM 節(jié)點(diǎn)的情況下,實(shí)際的交互方式則會(huì)因?yàn)g覽器而異。我們不推薦使用標(biāo)準(zhǔn) DOM 技術(shù)

修改<option>元素的文本或者值。

最后,我們還想提醒讀者注意一點(diǎn):選擇框的 change 事件與其他表單字段的 change 事件觸發(fā)的

條件不一樣。其他表單字段的 change 事件是在值被修改且焦點(diǎn)離開當(dāng)前字段時(shí)觸發(fā),而選擇框的

change 事件只要選中了選項(xiàng)就會(huì)觸發(fā)。

不同瀏覽器下,選項(xiàng)的 value 屬性返回什么值也存在差別。但是,在所有瀏覽

器中,value 屬性始終等于 value 特性。在未指定 value 特性的情況下,IE8 會(huì)返

回空字符串,而 IE9+、Safari、Firefox、Chrome 和 Opera 則會(huì)返回與 text 特性相同

的值。

14.3.1 選擇選項(xiàng)

對于只允許選擇一項(xiàng)的選擇框,訪問選中項(xiàng)的最簡單方式,就是使用選擇框的 selectedIndex 屬

性,如下面的例子所示:

var selectedOption = selectbox.options[selectbox.selectedIndex];

取得選中項(xiàng)之后,可以像下面這樣顯示該選項(xiàng)的信息:

var selectedIndex = selectbox.selectedIndex;

var selectedOption = selectbox.options[selectedIndex];

alert(\"Selected index: \" + selectedIndex + \"\

Selected text: \" +

selectedOption.text + \"\

Selected value: \" + selectedOption.value);

SelectboxExample01.htm

圖靈社區(qū)會(huì)員 StinkBC(StinkBC@gmail.com) 專享 尊重版權(quán)

百萬用戶使用云展網(wǎng)進(jìn)行書冊翻頁效果制作,只要您有文檔,即可一鍵上傳,自動(dòng)生成鏈接和二維碼(獨(dú)立電子書),支持分享到微信和網(wǎng)站!
收藏
轉(zhuǎn)發(fā)
下載
免費(fèi)制作
其他案例
更多案例
免費(fèi)制作
x
{{item.desc}}
下載
{{item.title}}
{{toast}}