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

javascript-gaojichengx有目錄u

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

javascript-gaojichengx有目錄u

16.2 原生拖放 483 1 15 16 45 13 67 8 9 1011 12以上代碼執(zhí)行后,你就會(huì)發(fā)現(xiàn)當(dāng)拖動(dòng)著元素移動(dòng)到放置目標(biāo)上時(shí),光標(biāo)變成了允許放置的符號(hào)。當(dāng)然,釋放鼠標(biāo)也會(huì)觸發(fā) drop 事件。在 Firefox 3.5+中,放置事件的默認(rèn)行為是打開被放到放置目標(biāo)上的 URL。換句話說(shuō),如果是把圖像拖放到放置目標(biāo)上,頁(yè)面就會(huì)轉(zhuǎn)向圖像文件;而如果是把文本拖放到放置目標(biāo)上,則會(huì)導(dǎo)致無(wú)效 URL錯(cuò)誤。因此,為了讓 Firefox 支持正常的拖放,還要取消 drop 事件的默認(rèn)行為,阻止它打開 URL:EventUtil.addHandler(droptarget, \"drop\", function(event){ EventUtil.preventDefault(event); }); 16.2.3 dataTransfer對(duì)象只有簡(jiǎn)單的拖放而沒有數(shù)據(jù)變化是沒有什么用的。為了在拖放操作時(shí)實(shí)現(xiàn)數(shù)據(jù)交換,IE 5 引入了dataTransfer 對(duì)象,它是事件對(duì)象的一個(gè)屬性,用于從被拖動(dòng)元素向放置目標(biāo)傳遞字符串格式的數(shù)據(jù)。因?yàn)樗鞘录?duì)象的... [收起]
[展開]
javascript-gaojichengx有目錄u
粉絲: {{bookData.followerCount}}
文本內(nèi)容
第501頁(yè)

16.2 原生拖放 483

1

15

16

4

5

13

6

7

8

9

10

11

12

以上代碼執(zhí)行后,你就會(huì)發(fā)現(xiàn)當(dāng)拖動(dòng)著元素移動(dòng)到放置目標(biāo)上時(shí),光標(biāo)變成了允許放置的符號(hào)。當(dāng)

然,釋放鼠標(biāo)也會(huì)觸發(fā) drop 事件。

在 Firefox 3.5+中,放置事件的默認(rèn)行為是打開被放到放置目標(biāo)上的 URL。換句話說(shuō),如果是把圖

像拖放到放置目標(biāo)上,頁(yè)面就會(huì)轉(zhuǎn)向圖像文件;而如果是把文本拖放到放置目標(biāo)上,則會(huì)導(dǎo)致無(wú)效 URL

錯(cuò)誤。因此,為了讓 Firefox 支持正常的拖放,還要取消 drop 事件的默認(rèn)行為,阻止它打開 URL:

EventUtil.addHandler(droptarget, \"drop\", function(event){

EventUtil.preventDefault(event);

});

16.2.3 dataTransfer對(duì)象

只有簡(jiǎn)單的拖放而沒有數(shù)據(jù)變化是沒有什么用的。為了在拖放操作時(shí)實(shí)現(xiàn)數(shù)據(jù)交換,IE 5 引入了

dataTransfer 對(duì)象,它是事件對(duì)象的一個(gè)屬性,用于從被拖動(dòng)元素向放置目標(biāo)傳遞字符串格式的數(shù)據(jù)。

因?yàn)樗鞘录?duì)象的屬性,所以只能在拖放事件的事件處理程序中訪問(wèn) dataTransfer 對(duì)象。在事件

處理程序中,可以使用這個(gè)對(duì)象的屬性和方法來(lái)完善拖放功能。目前,HTML5 規(guī)范草案也收入了

dataTransfer 對(duì)象。

dataTransfer 對(duì)象有兩個(gè)主要方法:getData()和 setData()。不難想象,getData()可以取

得由 setData()保存的值。setData()方法的第一個(gè)參數(shù),也是 getData()方法唯一的一個(gè)參數(shù),是

一個(gè)字符串,表示保存的數(shù)據(jù)類型,取值為\"text\"或\"URL\",如下所示:

//設(shè)置和接收文本數(shù)據(jù)

event.dataTransfer.setData(\"text\", \"some text\");

var text = event.dataTransfer.getData(\"text\");

//設(shè)置和接收 URL

event.dataTransfer.setData(\"URL\", \"http://www.wrox.com/\");

var url = event.dataTransfer.getData(\"URL\");

IE只定義了\"text\"和\"URL\"兩種有效的數(shù)據(jù)類型,而HTML5則對(duì)此加以擴(kuò)展,允許指定各種MIME

類型??紤]到向后兼容,HTML5 也支持\"text\"和\"URL\",但這兩種類型會(huì)被映射為\"text/plain\"和

\"text/uri-list\"。

實(shí)際上,dataTransfer 對(duì)象可以為每種 MIME 類型都保存一個(gè)值。換句話說(shuō),同時(shí)在這個(gè)對(duì)象

中保存一段文本和一個(gè) URL 不會(huì)有任何問(wèn)題。不過(guò),保存在 dataTransfer 對(duì)象中的數(shù)據(jù)只能在 drop

事件處理程序中讀取。如果在 ondrop 處理程序中沒有讀到數(shù)據(jù),那就是 dataTransfer 對(duì)象已經(jīng)被

銷毀,數(shù)據(jù)也丟失了。

在拖動(dòng)文本框中的文本時(shí),瀏覽器會(huì)調(diào)用 setData()方法,將拖動(dòng)的文本以\"text\"格式保存在

dataTransfer 對(duì)象中。類似地,在拖放鏈接或圖像時(shí),會(huì)調(diào)用 setData()方法并保存 URL。然后,

在這些元素被拖放到放置目標(biāo)時(shí),就可以通過(guò) getData()讀到這些數(shù)據(jù)。當(dāng)然,作為開發(fā)人員,你也

可以在 dragstart 事件處理程序中調(diào)用 setData(),手工保存自己要傳輸?shù)臄?shù)據(jù),以便將來(lái)使用。

將數(shù)據(jù)保存為文本和保存為 URL 是有區(qū)別的。如果將數(shù)據(jù)保存為文本格式,那么數(shù)據(jù)不會(huì)得到任

何特殊處理。而如果將數(shù)據(jù)保存為 URL,瀏覽器會(huì)將其當(dāng)成網(wǎng)頁(yè)中的鏈接。換句話說(shuō),如果你把它放置

到另一個(gè)瀏覽器窗口中,瀏覽器就會(huì)打開該 URL。

Firefox 在其第 5 個(gè)版本之前不能正確地將 \"url\" 和 \"text\" 映射為 \"text/uri-list\" 和

\"text/plain\"。但是卻能把\"Text\"(T 大寫)映射為\"text/plain\"。為了更好地在跨瀏覽器的情況

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

第502頁(yè)

484 第 16 章 HTML5 腳本編程

下從 dataTransfer 對(duì)象取得數(shù)據(jù),最好在取得 URL 數(shù)據(jù)時(shí)檢測(cè)兩個(gè)值,而在取得文本數(shù)據(jù)時(shí)使用

\"Text\"。

var dataTransfer = event.dataTransfer;

//讀取 URL

var url = dataTransfer.getData(\"url\") ||dataTransfer.getData(\"text/uri-list\");

//讀取文本

var text = dataTransfer.getData(\"Text\");

DataTransferExample01.htm

注意,一定要把短數(shù)據(jù)類型放在前面,因?yàn)?IE 10 及之前的版本仍然不支持?jǐn)U展的 MIME 類型名,

而它們?cè)谟龅綗o(wú)法識(shí)別的數(shù)據(jù)類型時(shí),會(huì)拋出錯(cuò)誤。

16.2.4 dropEffect與effectAllowed

利用 dataTransfer 對(duì)象,可不光是能夠傳輸數(shù)據(jù),還能通過(guò)它來(lái)確定被拖動(dòng)的元素以及作為放

置目標(biāo)的元素能夠接收什么操作。為此,需要訪問(wèn) dataTransfer 對(duì)象的兩個(gè)屬性:dropEffect 和

effectAllowed。

其中,通過(guò) dropEffect 屬性可以知道被拖動(dòng)的元素能夠執(zhí)行哪種放置行為。這個(gè)屬性有下列 4

個(gè)可能的值。

? \"none\":不能把拖動(dòng)的元素放在這里。這是除文本框之外所有元素的默認(rèn)值。

? \"move\":應(yīng)該把拖動(dòng)的元素移動(dòng)到放置目標(biāo)。

? \"copy\":應(yīng)該把拖動(dòng)的元素復(fù)制到放置目標(biāo)。

? \"link\":表示放置目標(biāo)會(huì)打開拖動(dòng)的元素(但拖動(dòng)的元素必須是一個(gè)鏈接,有 URL)。

在把元素拖動(dòng)到放置目標(biāo)上時(shí),以上每一個(gè)值都會(huì)導(dǎo)致光標(biāo)顯示為不同的符號(hào)。然而,要怎樣實(shí)現(xiàn)

光標(biāo)所指示的動(dòng)作完全取決于你。換句話說(shuō),如果你不介入,沒有什么會(huì)自動(dòng)地移動(dòng)、復(fù)制,也不會(huì)打

開鏈接。總之,瀏覽器只能幫你改變光標(biāo)的樣式,而其他的都要靠你自己來(lái)實(shí)現(xiàn)。要使用 dropEffect

屬性,必須在 ondragenter 事件處理程序中針對(duì)放置目標(biāo)來(lái)設(shè)置它。

dropEffect 屬性只有搭配 effectAllowed 屬性才有用。effectAllowed 屬性表示允許拖動(dòng)元

素的哪種 dropEffect,effectAllowed 屬性可能的值如下。

? \"uninitialized\":沒有給被拖動(dòng)的元素設(shè)置任何放置行為。

? \"none\":被拖動(dòng)的元素不能有任何行為。

? \"copy\":只允許值為\"copy\"的 dropEffect。

? \"link\":只允許值為\"link\"的 dropEffect。

? \"move\":只允許值為\"move\"的 dropEffect。

? \"copyLink\":允許值為\"copy\"和\"link\"的 dropEffect。

? \"copyMove\":允許值為\"copy\"和\"move\"的 dropEffect。

? \"linkMove\":允許值為\"link\"和\"move\"的 dropEffect。

? \"all\":允許任意 dropEffect。

必須在 ondragstart 事件處理程序中設(shè)置 effectAllowed 屬性。

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

第503頁(yè)

16.2 原生拖放 485

1

15

16

4

5

13

6

7

8

9

10

11

12

假設(shè)你想允許用戶把文本框中的文本拖放到一個(gè)<div>元素中。首先,必須將 dropEffect 和

effectAllowed 設(shè)置為\"move\"。但是,由于<div>元素的放置事件的默認(rèn)行為是什么也不做,所以文

本不可能自動(dòng)移動(dòng)。重寫這個(gè)默認(rèn)行為,就能從文本框中移走文本。然后你就可以自己編寫代碼將文本

插入到<div>中,這樣整個(gè)拖放操作就完成了。如果你將 dropEffect 和 effectAllowed 的值設(shè)置為

\"copy\",那就不會(huì)自動(dòng)移走文本框中的文本。

Firefox 5 及之前的版本在處理 effectAllowed 屬性時(shí)有一個(gè)問(wèn)題,即如果你在

代碼中設(shè)置了這個(gè)屬性的值,那不一定會(huì)觸發(fā) drop 事件。

16.2.5 可拖動(dòng)

默認(rèn)情況下,圖像、鏈接和文本是可以拖動(dòng)的,也就是說(shuō),不用額外編寫代碼,用戶就可以拖動(dòng)它

們。文本只有在被選中的情況下才能拖動(dòng),而圖像和鏈接在任何時(shí)候都可以拖動(dòng)。

讓其他元素可以拖動(dòng)也是可能的。HTML5 為所有 HTML 元素規(guī)定了一個(gè) draggable 屬性,表

示元素是否可以拖動(dòng)。圖像和鏈接的 draggable 屬性自動(dòng)被設(shè)置成了 true,而其他元素這個(gè)屬性

的默認(rèn)值都是 false。要想讓其他元素可拖動(dòng),或者讓圖像或鏈接不能拖動(dòng),都可以設(shè)置這個(gè)屬性。

例如:

<!-- 讓這個(gè)圖像不可以拖動(dòng) -->

<img src=\"smile.gif\" draggable=\"false\" alt=\"Smiley face\">

<!-- 讓這個(gè)元素可以拖動(dòng) -->

<div draggable=\"true\">...</div>

支持 draggable 屬性的瀏覽器有 IE 10+、Firefox 4+、Safari 5+和 Chrome。Opera 11.5 及之前的版

本都不支持 HTML5 的拖放功能。另外,為了讓 Firefox 支持可拖動(dòng)屬性,還必須添加一個(gè) ondragstart

事件處理程序,并在 dataTransfer 對(duì)象中保存一些信息。

在 IE9 及更早版本中,通過(guò) mousedown 事件處理程序調(diào)用 dragDrop()能夠讓

任何元素可拖動(dòng)。而在 Safari 4 及之前版本中,必須額外給相應(yīng)元素設(shè)置 CSS 樣式

–khtml-user-drag: element。

16.2.6 其他成員

HTML5 規(guī)范規(guī)定 dataTransfer 對(duì)象還應(yīng)該包含下列方法和屬性。

? addElement(element):為拖動(dòng)操作添加一個(gè)元素。添加這個(gè)元素只影響數(shù)據(jù)(即增加作為拖

動(dòng)源而響應(yīng)回調(diào)的對(duì)象),不會(huì)影響拖動(dòng)操作時(shí)頁(yè)面元素的外觀。在寫作本書時(shí),只有 Firefox 3.5+

實(shí)現(xiàn)了這個(gè)方法。

? clearData(format):清除以特定格式保存的數(shù)據(jù)。實(shí)現(xiàn)這個(gè)方法的瀏覽器有 IE、Fireforx 3.5+、

Chrome 和 Safari 4+。

? setDragImage(element, x, y):指定一幅圖像,當(dāng)拖動(dòng)發(fā)生時(shí),顯示在光標(biāo)下方。這個(gè)方

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

第504頁(yè)

486 第 16 章 HTML5 腳本編程

法接收的三個(gè)參數(shù)分別是要顯示的 HTML 元素和光標(biāo)在圖像中的 x、y 坐標(biāo)。其中,HTML 元素

可以是一幅圖像,也可以是其他元素。是圖像則顯示圖像,是其他元素則顯示渲染后的元素。

實(shí)現(xiàn)這個(gè)方法的瀏覽器有 Firefox 3.5+、Safari 4+和 Chrome。

? types:當(dāng)前保存的數(shù)據(jù)類型。這是一個(gè)類似數(shù)組的集合,以\"text\"這樣的字符串形式保存著

數(shù)據(jù)類型。實(shí)現(xiàn)這個(gè)屬性的瀏覽器有 IE10+、Firefox 3.5+和 Chrome。

16.3 媒體元素

隨著音頻和視頻在 Web 上的迅速流行,大多數(shù)提供富媒體內(nèi)容的站點(diǎn)為了保證跨瀏覽器兼容性,

不得不選擇使用 Flash。HTML5 新增了兩個(gè)與媒體相關(guān)的標(biāo)簽,讓開發(fā)人員不必依賴任何插件就能在網(wǎng)

頁(yè)中嵌入跨瀏覽器的音頻和視頻內(nèi)容。這兩個(gè)標(biāo)簽就是<audio>和<video>。

這兩個(gè)標(biāo)簽除了能讓開發(fā)人員方便地嵌入媒體文件之外,都提供了用于實(shí)現(xiàn)常用功能的 JavaScript

API,允許為媒體創(chuàng)建自定義的控件。這兩個(gè)元素的用法如下。

<!-- 嵌入視頻 -->

<video src=\"conference.mpg\" id=\"myVideo\">Video player not available.</video>

<!-- 嵌入音頻 -->

<audio src=\"song.mp3\" id=\"myAudio\">Audio player not available.</audio>

使用這兩個(gè)元素時(shí),至少要在標(biāo)簽中包含 src 屬性,指向要加載的媒體文件。還可以設(shè)置 width

和 height 屬性以指定視頻播放器的大小,而為 poster 屬性指定圖像的 URI 可以在加載視頻內(nèi)容期間

顯示一幅圖像。另外,如果標(biāo)簽中有 controls 屬性,則意味著瀏覽器應(yīng)該顯示 UI 控件,以便用戶直

接操作媒體。位于開始和結(jié)束標(biāo)簽之間的任何內(nèi)容都將作為后備內(nèi)容,在瀏覽器不支持這兩個(gè)媒體元素

的情況下顯示。

因?yàn)椴⒎撬袨g覽器都支持所有媒體格式,所以可以指定多個(gè)不同的媒體來(lái)源。為此,不用在標(biāo)簽

中指定 src 屬性,而是要像下面這樣使用一或多個(gè)<source>元素。

<!-- 嵌入視頻 -->

<video id=\"myVideo\">

<source src=\"conference.webm\" type=\"video/webm; codecs='vp8, vorbis'\">

<source src=\"conference.ogv\" type=\"video/ogg; codecs='theora, vorbis'\">

<source src=\"conference.mpg\">

Video player not available.

</video>

<!-- 嵌入音頻 -->

<audio id=\"myAudio\">

<source src=\"song.ogg\" type=\"audio/ogg\">

<source src=\"song.mp3\" type=\"audio/mpeg\">

Audio player not available.

</audio>

關(guān)于視頻和音頻編解碼器的內(nèi)容超出了本書討論的范圍。作者在此只想告訴大家,不同的瀏覽器支

持不同的編解碼器,因此一般來(lái)說(shuō)指定多種格式的媒體來(lái)源是必需的。支持這兩個(gè)媒體元素的瀏覽器有

IE9+、Firefox 3.5+、Safari 4+、Opera 10.5+、Chrome、iOS 版 Safari 和 Android 版 WebKit。

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

第505頁(yè)

16.3 媒體元素 487

1

15

16

4

5

13

6

7

8

9

10

11

12

16.3.1 屬性

<video>和<audio>元素都提供了完善的 JavaScript 接口。下表列出了這兩個(gè)元素共有的屬性,通

過(guò)這些屬性可以知道媒體的當(dāng)前狀態(tài)。

屬 性 數(shù)據(jù)類型 說(shuō) 明

autoplay 布爾值 取得或設(shè)置autoplay標(biāo)志

buffered 時(shí)間范圍 表示已下載的緩沖的時(shí)間范圍的對(duì)象

bufferedBytes 字節(jié)范圍 表示已下載的緩沖的字節(jié)范圍的對(duì)象

bufferingRate 整數(shù) 下載過(guò)程中每秒鐘平均接收到的位數(shù)

bufferingThrottled 布爾值 表示瀏覽器是否對(duì)緩沖進(jìn)行了節(jié)流

controls 布爾值 取得或設(shè)置controls屬性,用于顯示或隱藏瀏覽器內(nèi)置的控件

currentLoop 整數(shù) 媒體文件已經(jīng)循環(huán)的次數(shù)

currentSrc 字符串 當(dāng)前播放的媒體文件的URL

currentTime 浮點(diǎn)數(shù) 已經(jīng)播放的秒數(shù)

defaultPlaybackRate 浮點(diǎn)數(shù) 取得或設(shè)置默認(rèn)的播放速度。默認(rèn)值為1.0秒

duration 浮點(diǎn)數(shù) 媒體的總播放時(shí)間(秒數(shù))

ended 布爾值 表示媒體文件是否播放完成

loop 布爾值 取得或設(shè)置媒體文件在播放完成后是否再?gòu)念^開始播放

muted 布爾值 取得或設(shè)置媒體文件是否靜音

networkState 整數(shù) 表示當(dāng)前媒體的網(wǎng)絡(luò)連接狀態(tài):0表示空,1表示正在加載,2表示

正在加載元數(shù)據(jù),3表示已經(jīng)加載了第一幀,4表示加載完成

paused 布爾值 表示播放器是否暫停

playbackRate 浮點(diǎn)數(shù) 取得或設(shè)置當(dāng)前的播放速度。用戶可以改變這個(gè)值,讓媒體播放速

度變快或變慢,這與defaultPlaybackRate只能由開發(fā)人員修改

的defaultPlaybackRate不同

played 時(shí)間范圍 到目前為止已經(jīng)播放的時(shí)間范圍

readyState 整數(shù) 表示媒體是否已經(jīng)就緒(可以播放了)。0表示數(shù)據(jù)不可用,1表示

可以顯示當(dāng)前幀,2表示可以開始播放,3表示媒體可以從頭到尾播放

seekable 時(shí)間范圍 可以搜索的時(shí)間范圍

seeking 布爾值 表示播放器是否正移動(dòng)到媒體文件中的新位置

src 字符串 媒體文件的來(lái)源。任何時(shí)候都可以重寫這個(gè)屬性

start 浮點(diǎn)數(shù) 取得或設(shè)置媒體文件中開始播放的位置,以秒表示

totalBytes 整數(shù) 當(dāng)前資源所需的總字節(jié)數(shù)

videoHeight 整數(shù) 返回視頻(不一定是元素)的高度。只適用于<video>

videoWidth 整數(shù) 返回視頻(不一定是元素)的寬度。只適用于<video>

volume 浮點(diǎn)數(shù) 取得或設(shè)置當(dāng)前音量,值為0.0到1.0

其中很多屬性也可以直接在<audio>和<video>元素中設(shè)置。

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

第506頁(yè)

488 第 16 章 HTML5 腳本編程

16.3.2 事件

除了大量屬性之外,這兩個(gè)媒體元素還可以觸發(fā)很多事件。這些事件監(jiān)控著不同的屬性的變化,這

些變化可能是媒體播放的結(jié)果,也可能是用戶操作播放器的結(jié)果。下表列出了媒體元素相關(guān)的事件。

事 件 觸發(fā)時(shí)機(jī)

abort 下載中斷

canplay 可以播放時(shí);readyState值為2

canplaythrough 播放可繼續(xù),而且應(yīng)該不會(huì)中斷;readyState值為3

canshowcurrentframe 當(dāng)前幀已經(jīng)下載完成;readyState值為1

dataunavailable 因?yàn)闆]有數(shù)據(jù)而不能播放;readyState值為0

durationchange duration屬性的值改變

emptied 網(wǎng)絡(luò)連接關(guān)閉

empty 發(fā)生錯(cuò)誤阻止了媒體下載

ended 媒體已播放到末尾,播放停止

error 下載期間發(fā)生網(wǎng)絡(luò)錯(cuò)誤

load 所有媒體已加載完成。這個(gè)事件可能會(huì)被廢棄,建議使用canplaythrough

loadeddata 媒體的第一幀已加載完成

loadedmetadata 媒體的元數(shù)據(jù)已加載完成

loadstart 下載已開始

pause 播放已暫停

play 媒體已接收到指令開始播放

playing 媒體已實(shí)際開始播放

progress 正在下載

ratechange 播放媒體的速度改變

seeked 搜索結(jié)束

seeking 正移動(dòng)到新位置

stalled 瀏覽器嘗試下載,但未接收到數(shù)據(jù)

timeupdate currentTime被以不合理或意外的方式更新

volumechange volume屬性值或muted屬性值已改變

waiting 播放暫停,等待下載更多數(shù)據(jù)

這些事件之所以如此具體,就是為了讓開發(fā)人員只使用少量 HTML 和 JavaScript(與創(chuàng)建 Flash 影

片相比)即可編寫出自定義的音頻/視頻播放器。

16.3.3 自定義媒體播放器

使用<audio>和<video>元素的 play()和 pause()方法,可以手工控制媒體文件的播放。組合使

用屬性、事件和這兩個(gè)方法,很容易創(chuàng)建一個(gè)自定義的媒體播放器,如下面的例子所示。

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

第507頁(yè)

16.3 媒體元素 489

1

15

16

4

5

13

6

7

8

9

10

11

12

<div class=\"mediaplayer\">

<div class=\"video\">

<video id=\"player\" src=\"movie.mov\" poster=\"mymovie.jpg\"

width=\"300\" height=\"200\">

Video player not available.

</video>

</div>

<div class=\"controls\">

<input type=\"button\" value=\"Play\" id=\"video-btn\">

<span id=\"curtime\">0</span>/<span id=\"duration\">0</span>

</div>

</div>

VideoPlayerExample01.htm

以上基本的 HTML 再加上一些 JavaScript 就可以變成一個(gè)簡(jiǎn)單的視頻播放器。以下就是 JavaScript

代碼。

//取得元素的引用

var player = document.getElementById(\"player\"),

btn = document.getElementById(\"video-btn\"),

curtime = document.getElementById(\"curtime\"),

duration = document.getElementById(\"duration\");

//更新播放時(shí)間

duration.innerHTML = player.duration;

//為按鈕添加事件處理程序

EventUtil.addHandler(btn, \"click\", function(event){

if (player.paused){

player.play();

btn.value = \"Pause\";

} else {

player.pause();

btn.value = \"Play\";

}

});

//定時(shí)更新當(dāng)前時(shí)間

setInterval(function(){

curtime.innerHTML = player.currentTime;

}, 250);

VideoPlayerExample01.htm

以上 JavaScript 代碼給按鈕添加了一個(gè)事件處理程序,單擊它能讓視頻在暫停時(shí)播放,在播放時(shí)暫

停。通過(guò)<video>元素的 load 事件處理程序,設(shè)置了加載完視頻后顯示播放時(shí)間。最后,設(shè)置了一個(gè)

計(jì)時(shí)器,以更新當(dāng)前顯示的時(shí)間。你可以進(jìn)一步擴(kuò)展這個(gè)視頻播放器,監(jiān)聽更多事件,利用更多屬性。

而同樣的代碼也可以用于<audio>元素,以創(chuàng)建自定義的音頻播放器。

16.3.4 檢測(cè)編解碼器的支持情況

如前所述,并非所有瀏覽器都支持<video>和<audio>的所有編解碼器,而這基本上就意味著你必

須提供多個(gè)媒體來(lái)源。不過(guò),也有一個(gè) JavaScript API 能夠檢測(cè)瀏覽器是否支持某種格式和編解碼器。

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

第508頁(yè)

490 第 16 章 HTML5 腳本編程

這兩個(gè)媒體元素都有一個(gè) canPlayType()方法,該方法接收一種格式/編解碼器字符串,返回

\"probably\"、\"maybe\"或\"\"( 空字符串)??兆址羌僦?,因此可以像下面這樣在 if 語(yǔ)句中使用

canPlayType():

if (audio.canPlayType(\"audio/mpeg\")){

//進(jìn)一步處理

}

而\"probably\"和\"maybe\"都是真值,因此在 if 語(yǔ)句的條件測(cè)試中可以轉(zhuǎn)換成 true。

如果給 canPlayType()傳入了一種 MIME 類型,則返回值很可能是\"maybe\"或空字符串。這是因

為媒體文件本身只不過(guò)是音頻或視頻的一個(gè)容器,而真正決定文件能否播放的還是編碼的格式。在同時(shí)

傳入 MIME 類型和編解碼器的情況下,可能性就會(huì)增加,返回的字符串會(huì)變成\"probably\"。下面來(lái)看

幾個(gè)例子。

var audio = document.getElementById(\"audio-player\");

//很可能\"maybe\"

if (audio.canPlayType(\"audio/mpeg\")){

//進(jìn)一步處理

}

//可能是\"probably\"

if (audio.canPlayType(\"audio/ogg; codecs=\\\"vorbis\\\"\")){

//進(jìn)一步處理

}

注意,編解碼器必須用引號(hào)引起來(lái)才行。下表列出了已知的已得到支持的音頻格式和編解碼器。

音 頻 字 符 串 支持的瀏覽器

AAC audio/mp4; codecs=\"mp4a.40.2\" IE9+、Safari 4+、iOS版Safari

MP3 audio/mpeg IE9+、Chrome

Vorbis audio/ogg; codecs=\"vorbis\" Firefox 3.5+、Chrome、Opera 10.5+

WAV audio/wav; codecs=\"1\" Firefox 3.5+、Opera 10.5+、Chrome

當(dāng)然,也可以使用 canPlayType()來(lái)檢測(cè)視頻格式。下表列出了已知的已得到支持的音頻格式和

編解碼器。

視 頻 字 符 串 支持的瀏覽器

H.264 video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\" IE9+、Safari 4+、iOS版Safari、Android

版WebKit

Theora video/ogg; codecs=\"theora\" Firefox 3.5+、Opera 10.5、Chrome

WebM video/webm; codecs=\"vp8, vorbis\" Firefox 4+、Opera 10.6、Chrome

16.3.5 Audio類型

<audio>元素還有一個(gè)原生的 JavaScript 構(gòu)造函數(shù) Audio,可以在任何時(shí)候播放音頻。從同為 DOM

元素的角度看,Audio 與 Image 很相似,但 Audio 不用像 Image 那樣必須插入到文檔中。只要?jiǎng)?chuàng)建一

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

第509頁(yè)

16.4 歷史狀態(tài)管理 491

1

15

16

4

5

13

6

7

8

9

10

11

12

個(gè)新實(shí)例,并傳入音頻源文件即可。

var audio = new Audio(\"sound.mp3\");

EventUtil.addHandler(audio, \"canplaythrough\", function(event){

audio.play();

});

創(chuàng)建新的 Audio 實(shí)例即可開始下載指定的文件。下載完成后,調(diào)用 play()就可以播放音頻。

在 iOS 中,調(diào)用 play()時(shí)會(huì)彈出一個(gè)對(duì)話框,得到用戶的許可后才能播放聲音。如果想在一段音

頻播放后再播放另一段音頻,必須在 onfinish 事件處理程序中調(diào)用 play()方法。

16.4 歷史狀態(tài)管理

歷史狀態(tài)管理是現(xiàn)代 Web 應(yīng)用開發(fā)中的一個(gè)難點(diǎn)。在現(xiàn)代 Web 應(yīng)用中,用戶的每次操作不一定會(huì)

打開一個(gè)全新的頁(yè)面,因此“后退”和“前進(jìn)”按鈕也就失去了作用,導(dǎo)致用戶很難在不同狀態(tài)間切換。

要解決這個(gè)問(wèn)題,首選使用 hashchange 事件(第 13 章曾討論過(guò))。HTML5 通過(guò)更新 history 對(duì)象為

管理歷史狀態(tài)提供了方便。

通過(guò) hashchange 事件,可以知道 URL 的參數(shù)什么時(shí)候發(fā)生了變化,即什么時(shí)候該有所反應(yīng)。而

通過(guò)狀態(tài)管理 API ,能夠在不加載新頁(yè)面的情況下改變?yōu)g覽器的 URL 。為此,需要使用

history.pushState()方法,該方法可以接收三個(gè)參數(shù):狀態(tài)對(duì)象、新狀態(tài)的標(biāo)題和可選的相對(duì) URL。

例如:

history.pushState({name:\"Nicholas\"}, \"Nicholas' page\", \"nicholas.html\");

執(zhí)行 pushState()方法后,新的狀態(tài)信息就會(huì)被加入歷史狀態(tài)棧,而瀏覽器地址欄也會(huì)變成新的

相對(duì) URL。但是,瀏覽器并不會(huì)真的向服務(wù)器發(fā)送請(qǐng)求,即使?fàn)顟B(tài)改變之后查詢 location.href 也會(huì)

返回與地址欄中相同的地址。另外,第二個(gè)參數(shù)目前還沒有瀏覽器實(shí)現(xiàn),因此完全可以只傳入一個(gè)空字

符串,或者一個(gè)短標(biāo)題也可以。而第一個(gè)參數(shù)則應(yīng)該盡可能提供初始化頁(yè)面狀態(tài)所需的各種信息。

因?yàn)?pushState()會(huì)創(chuàng)建新的歷史狀態(tài),所以你會(huì)發(fā)現(xiàn)“后退”按鈕也能使用了。按下“后退”

按鈕,會(huì)觸發(fā) window 對(duì)象的 popstate 事件①。popstate 事件的事件對(duì)象有一個(gè) state 屬性,這個(gè)

屬性就包含著當(dāng)初以第一個(gè)參數(shù)傳遞給 pushState()的狀態(tài)對(duì)象。

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

var state = event.state;

if (state){ //第一個(gè)頁(yè)面加載時(shí) state 為空

processState(state);

}

});

得到這個(gè)狀態(tài)對(duì)象后,必須把頁(yè)面重置為狀態(tài)對(duì)象中的數(shù)據(jù)表示的狀態(tài)(因?yàn)闉g覽器不會(huì)自動(dòng)為你

做這些)。記住,瀏覽器加載的第一個(gè)頁(yè)面沒有狀態(tài),因此單擊“后退”按鈕返回瀏覽器加載的第一個(gè)

頁(yè)面時(shí),event.state 值為 null。

要更新當(dāng)前狀態(tài),可以調(diào)用 replaceState(),傳入的參數(shù)與 pushState()的前兩個(gè)參數(shù)相同。

調(diào)用這個(gè)方法不會(huì)在歷史狀態(tài)棧中創(chuàng)建新狀態(tài),只會(huì)重寫當(dāng)前狀態(tài)。

——————————

① popstate 事件發(fā)生后,事件對(duì)象中的狀態(tài)對(duì)象(event.state)是當(dāng)前狀態(tài)。

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

第510頁(yè)

492 第 16 章 HTML5 腳本編程

history.replaceState({name:\"Greg\"}, \"Greg's page\");

支持 HTML5 歷史狀態(tài)管理的瀏覽器有 Firefox 4+、Safari 5+、Opera 11.5+和 Chrome。在 Safari 和

Chrome 中,傳遞給 pushState()或 replaceState()的狀態(tài)對(duì)象中不能包含 DOM 元素。而 Firefox

支持在狀態(tài)對(duì)象中包含 DOM 元素。Opera 還支持一個(gè) history.state 屬性,它返回當(dāng)前狀態(tài)的狀態(tài)

對(duì)象。

在使用 HTML5 的狀態(tài)管理機(jī)制時(shí),請(qǐng)確保使用 pushState()創(chuàng)造的每一個(gè)“假”

URL,在 Web 服務(wù)器上都有一個(gè)真的、實(shí)際存在的 URL 與之對(duì)應(yīng)。否則,單擊“刷

新”按鈕會(huì)導(dǎo)致 404 錯(cuò)誤。

16.5 小結(jié)

HTML5 除了定義了新的標(biāo)記規(guī)則,還定義了一些 JavaScript API。這些 API 是為了讓開發(fā)人員創(chuàng)建

出更好的、能夠與桌面應(yīng)用媲美的用戶界面而設(shè)計(jì)的。本章討論了如下 API。

? 跨文檔消息傳遞 API 能夠讓我們?cè)诓唤档屯床呗园踩缘那疤嵯?,在?lái)自不同域的文檔間傳

遞消息。

? 原生拖放功能讓我們可以方便地指定某個(gè)元素可拖動(dòng),并在操作系統(tǒng)要放置時(shí)做出響應(yīng)。還可

以創(chuàng)建自定義的可拖動(dòng)元素及放置目標(biāo)。

? 新的媒體元素<audio>和<video>擁有自己的與音頻和視頻交互的 API。并非所有瀏覽器支持所

有的媒體格式,因此應(yīng)該使用 canPlayType()檢查瀏覽器是否支持特定的格式。

? 歷史狀態(tài)管理讓我們不必卸載當(dāng)前頁(yè)面即可修改瀏覽器的歷史狀態(tài)棧。有了這種機(jī)制,用戶就

可以通過(guò)“后退”和“前進(jìn)”按鈕在頁(yè)面狀態(tài)間切換,而這些狀態(tài)完全由 JavaScript 進(jìn)行控制。

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

第511頁(yè)

17.1 瀏覽器報(bào)告的錯(cuò)誤 493

14

2

3

17

5

13

6

7

8

9

10

11

12

錯(cuò)誤處理與調(diào)試

本章內(nèi)容

? 理解瀏覽器報(bào)告的錯(cuò)誤

? 處理錯(cuò)誤

? 調(diào)試 JavaScript 代碼

于 JavaScript 本身是動(dòng)態(tài)語(yǔ)言,而且多年來(lái)一直沒有固定的開發(fā)工具,因此人們普遍認(rèn)為它是

一種最難于調(diào)試的編程語(yǔ)言。腳本出錯(cuò)時(shí),瀏覽器通常會(huì)給出類似于“object expected”(缺少

對(duì)象)這樣的消息,沒有上下文信息,讓人摸不著頭腦。ECMAScript 第 3 版致力于解決這個(gè)問(wèn)題,專

門引入了 try-catch 和 throw 語(yǔ)句以及一些錯(cuò)誤類型,意在讓開發(fā)人員能夠適當(dāng)?shù)靥幚礤e(cuò)誤。幾年之

后,Web 瀏覽器中也出現(xiàn)了一些 JavaScript 調(diào)試程序和工具。2008 年以來(lái),大多數(shù) Web 瀏覽器都已經(jīng)具

備了一些調(diào)試 JavaScript 代碼的能力。

在有了語(yǔ)言特性和工具支持之后,現(xiàn)在的開發(fā)人員已經(jīng)能夠適當(dāng)?shù)貙?shí)現(xiàn)錯(cuò)誤處理,并且能夠找到錯(cuò)

誤的根源。

17.1 瀏覽器報(bào)告的錯(cuò)誤

IE、Firefox、Safari、Chrome 和 Opera 等主流瀏覽器,都具有某種向用戶報(bào)告 JavaScript 錯(cuò)誤的機(jī)

制。默認(rèn)情況下,所有瀏覽器都會(huì)隱藏此類信息,畢竟除了開發(fā)人員之外,很少有人關(guān)心這些內(nèi)容。

因此,在基于瀏覽器編寫 JavaScript 腳本時(shí),別忘了啟用瀏覽器的 JavaScript 報(bào)告功能,以便及時(shí)收到

錯(cuò)誤通知。

17.1.1 IE

IE 是唯一一個(gè)在瀏覽器的界面窗體(chrome)中顯示 JavaScript 錯(cuò)誤信息的瀏覽器。在發(fā)生 JavaScript

錯(cuò)誤時(shí),瀏覽器左下角會(huì)出現(xiàn)一個(gè)黃色的圖標(biāo),圖標(biāo)旁邊則顯示著\"Error on page\"(頁(yè)面中有錯(cuò)誤)。

假如不是存心去看的話,你很可能不會(huì)注意這個(gè)圖標(biāo)。雙擊這個(gè)圖標(biāo),就會(huì)看到一個(gè)包含錯(cuò)誤消息的對(duì)

話框,其中還包含諸如行號(hào)、字符數(shù)、錯(cuò)誤代碼及文件名(其實(shí)就是你在查看的頁(yè)面的 URL)等相關(guān)信

息。圖 17-1 展示了 IE 的錯(cuò)誤消息對(duì)話框。

這些信息對(duì)于一般用戶還算說(shuō)得過(guò)去,但對(duì) Web 開發(fā)來(lái)說(shuō)就遠(yuǎn)遠(yuǎn)不夠了。可以通過(guò)設(shè)置讓錯(cuò)誤對(duì)

話框一發(fā)生錯(cuò)誤就顯示出來(lái)。為此,要打開“Tools”(工具)菜單中的“Internet Options”(Internet 選項(xiàng))

對(duì)話框,切換到“Advanced”(高級(jí))選項(xiàng)卡,選中“Display a notification about every script error”(顯

示每個(gè)腳本錯(cuò)誤的通知)復(fù)選框(參見圖 17-2)。單擊“OK”(確定)按鈕保存設(shè)置。

第 17 章

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

第512頁(yè)

494 第 17 章 錯(cuò)誤處理與調(diào)試

圖 17-1

圖 17-2

保存了設(shè)置之后,通常要雙擊黃色圖標(biāo)才會(huì)顯示的對(duì)話

框,就會(huì)變成一有錯(cuò)誤發(fā)生隨即自動(dòng)顯示出來(lái)。

另外,如果啟用了腳本調(diào)試功能的話(默認(rèn)是禁用的),那

么在發(fā)生錯(cuò)誤時(shí),你不僅會(huì)顯示錯(cuò)誤通知,而且還會(huì)看到另一

個(gè)對(duì)話框,詢問(wèn)是否想要調(diào)試錯(cuò)誤(參見圖 17-3)。

要啟用腳本調(diào)試功能,必須要在 IE 中安裝某種腳本調(diào)試

器。(IE8 和 IE9 自帶調(diào)試器。)本章后面會(huì)單獨(dú)討論調(diào)試器。

在 IE7 及更早版本中,如果錯(cuò)誤發(fā)生在位于外部文件的腳本中,行號(hào)通常會(huì)與

錯(cuò)誤所在的行號(hào)差 1。如果是嵌入在頁(yè)面中的腳本發(fā)生錯(cuò)誤,則行號(hào)就是錯(cuò)誤所在的

行號(hào)。

17.1.2 Firefox

默認(rèn)情況下,F(xiàn)irefox 在 JavaScript 發(fā)生錯(cuò)誤時(shí)不會(huì)通過(guò)瀏覽器界面給出提示。但它會(huì)在后臺(tái)將錯(cuò)誤

圖 17-3

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

第513頁(yè)

17.1 瀏覽器報(bào)告的錯(cuò)誤 495

14

2

3

17

5

13

6

7

8

9

10

11

12

記錄到錯(cuò)誤控制臺(tái)中。單擊“Tools”(工具)菜單中的“Error Console”(錯(cuò)誤控制臺(tái))可以顯示錯(cuò)誤控

制臺(tái)(見圖 17-4)。你會(huì)發(fā)現(xiàn),錯(cuò)誤控制臺(tái)中實(shí)際上還包含與 JavaScript、CSS 和 HTML 相關(guān)的警告和

信息,可以通過(guò)篩選找到錯(cuò)誤。

圖 17-4

在發(fā)生 JavaScript 錯(cuò)誤時(shí),F(xiàn)irefox 會(huì)將其記錄為一個(gè)錯(cuò)誤,包括錯(cuò)誤消息、引發(fā)錯(cuò)誤的 URL 和錯(cuò)誤

所在的行號(hào)等信息。單擊文件名即可以只讀方式打開發(fā)生錯(cuò)誤的腳本,發(fā)生錯(cuò)誤的代碼行會(huì)突出顯示。

目前,最流行的 Firefox 插件 Firebug,已經(jīng)成為開發(fā)人員必備的 JavaScript 糾錯(cuò)工具。這個(gè)可以從

www.getfirebug.com 下載到的插件,會(huì)在 Firefox 狀態(tài)欄的右下角區(qū)域添加一個(gè)圖標(biāo)。默認(rèn)情況下,右下

角區(qū)域顯示的是一個(gè)綠色對(duì)勾圖標(biāo)。在有 JavaScript 錯(cuò)誤發(fā)生時(shí),圖標(biāo)會(huì)變成紅叉,同時(shí)旁邊顯示錯(cuò)誤

的數(shù)量。單擊這個(gè)紅叉會(huì)打開 Firebug 控制臺(tái),其中顯示有錯(cuò)誤消息、錯(cuò)誤所在的代碼行(不包含上下

文)、錯(cuò)誤所在的 URL 以及行號(hào)(參見圖 17-5)。

圖 17-5

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

第514頁(yè)

496 第 17 章 錯(cuò)誤處理與調(diào)試

在 Firebug 中單擊導(dǎo)致錯(cuò)誤的代碼行,將在一個(gè)新 Firebug 視圖中打開整個(gè)腳本,該代碼行在其中突

出顯示。

除了顯示錯(cuò)誤之外,F(xiàn)irebug 還有更多的用處。實(shí)際上,它還是針對(duì) Firefox 的成

熟的調(diào)試環(huán)境,為調(diào)試 JavaScript、CSS、DOM 和網(wǎng)絡(luò)連接錯(cuò)誤提供了諸多功能。

17.1.3 Safari

Windows 和 Mac OS 平臺(tái)的 Safari 在默認(rèn)情況下都會(huì)隱藏全部 JavaScript 錯(cuò)誤。為了訪問(wèn)到這些信

息,必須啟用“Develop”(開發(fā))菜單。為此,需要單擊“Edit”(編輯)菜單中的“Preferences”(偏

好設(shè)置),然后在“Advanced”(高級(jí))選項(xiàng)卡中,選中“Show develop menu in menubar”(在菜單欄中

顯示“開發(fā)”菜單)。啟用此項(xiàng)設(shè)置之后,就會(huì)在 Safari 的菜單欄中看到一個(gè)“Develop”菜單(參見

圖 17-6)。

圖 17-6

“Develop”菜單中提供了一些與調(diào)試有關(guān)的選項(xiàng),還有一些選項(xiàng)可以影響當(dāng)前加載的頁(yè)面。單擊

“Show Error Console”(顯示錯(cuò)誤控制臺(tái))選項(xiàng),將會(huì)看到一組 JavaScript 及其他錯(cuò)誤??刂婆_(tái)中顯示著

錯(cuò)誤消息、錯(cuò)誤的 URL 及錯(cuò)誤的行號(hào)(參見圖 17-7)。

單擊控制臺(tái)中的錯(cuò)誤消息,就可以打開導(dǎo)致錯(cuò)誤的源代碼。除了被輸出到控制臺(tái)之外,JavaScript

錯(cuò)誤不會(huì)影響 Safari 窗口的外觀。

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

第515頁(yè)

17.1 瀏覽器報(bào)告的錯(cuò)誤 497

14

2

3

17

5

13

6

7

8

9

10

11

12

圖 17-7

17.1.4 Opera

Opera 在默認(rèn)情況下也會(huì)隱藏 JavaScript 錯(cuò)誤,所有錯(cuò)誤都會(huì)被記錄到錯(cuò)誤控制臺(tái)中。要打開錯(cuò)誤

控制臺(tái),需要單擊“Tools”(工具)菜單,在“Advanced”(高級(jí))子菜單項(xiàng)下面再單擊“Error Console”

(錯(cuò)誤控制臺(tái))。與 Firefox 一樣,Opera 的錯(cuò)誤控制臺(tái)中也包含了除 JavaScript 錯(cuò)誤之外的很多來(lái)源(如

HTML、CSS、XML、XSLT 等)的錯(cuò)誤和警告信息。要分類查看不同來(lái)源的消息,可以使用左下角的

下拉選擇框(參見圖 17-8)。

錯(cuò)誤消息中顯示著導(dǎo)致錯(cuò)誤的 URL 和錯(cuò)誤所在的線程。有時(shí)候,還會(huì)有棧跟蹤信息。除了錯(cuò)誤控

制臺(tái)中顯示的信息之外,沒有其他途徑可以獲得更多信息。

也可以讓 Opera 一發(fā)生錯(cuò)誤就彈出錯(cuò)誤控制臺(tái)。為此,要在“Tools”(工具)菜單中單擊“Preferences”

(首選項(xiàng)),再單擊“Advanced”(高級(jí))選項(xiàng)卡,然后從左側(cè)菜單中選擇“Content”(內(nèi)容)。單擊“JavaScrip

Options”(JavaScript 選項(xiàng))按鈕,顯示選項(xiàng)對(duì)話框(如圖 17-9 所示)。

在這個(gè)選項(xiàng)對(duì)話框中,選中“Open console on error”(出錯(cuò)時(shí)打開控制臺(tái)),單擊“OK”(確定)

按鈕。這樣,每當(dāng)發(fā)生 JavaScript 錯(cuò)誤時(shí),就會(huì)彈出錯(cuò)誤控制臺(tái)。另外,還可以針對(duì)特定的站點(diǎn)來(lái)作

此設(shè)置,方法是單擊“Tools”(工具)、“Quick Preferences”(快速參數(shù))、“Edit Site Preferences”(編

輯站點(diǎn)首選項(xiàng)),選擇“Scripting”(腳本)選項(xiàng)卡,最后選中“Open console on error”(出錯(cuò)時(shí)打開

控制臺(tái))。

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

第516頁(yè)

498 第 17 章 錯(cuò)誤處理與調(diào)試

圖 17-8 圖 17-9

17.1.5 Chrome

與 Safari 和 Opera 一樣,Chrome 在默認(rèn)情況下也會(huì)隱藏 JavaScript 錯(cuò)誤。所有錯(cuò)誤都將被記錄到

Web Inspector 控制臺(tái)中。要查看錯(cuò)誤消息,必須打開 Web Inspector。為此,要單擊位于地址欄右側(cè)的

“Control this page”(控制當(dāng)前頁(yè))按鈕,選擇“Developer”(開發(fā)人員)、“JavaScript console”(JavaScript

控制臺(tái)),參見圖 17-10。

圖 17-10

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

第517頁(yè)

17.2 錯(cuò)誤處理 499

14

2

3

17

5

13

6

7

8

9

10

11

12

打開的 Web Inspector 中包含著有關(guān)頁(yè)面的信息和 JavaScript 控制臺(tái)??刂婆_(tái)中顯示著錯(cuò)誤消息、錯(cuò)

誤的 URL 和錯(cuò)誤的行號(hào)(參見圖 17-11)。

圖 17-11

單擊 JavaScript 控制臺(tái)中的錯(cuò)誤,就可以定位到導(dǎo)致錯(cuò)誤的源代碼行。

17.2 錯(cuò)誤處理

錯(cuò)誤處理在程序設(shè)計(jì)中的重要性是勿庸置疑的。任何有影響力的 Web 應(yīng)用程序都需要一套完善的

錯(cuò)誤處理機(jī)制,當(dāng)然,大多數(shù)佼佼者確實(shí)做到了這一點(diǎn),但通常只有服務(wù)器端應(yīng)用程序才能做到如此。

實(shí)際上,服務(wù)器端團(tuán)隊(duì)往往會(huì)在錯(cuò)誤處理機(jī)制上投入較大的精力,通常要考慮按照類型、頻率,或者其

他重要的標(biāo)準(zhǔn)對(duì)錯(cuò)誤進(jìn)行分類。這樣一來(lái),開發(fā)人員就能夠理解用戶在使用簡(jiǎn)單數(shù)據(jù)庫(kù)查詢或者報(bào)告生

成腳本時(shí),應(yīng)用程序可能會(huì)出現(xiàn)的問(wèn)題。

雖然客戶端應(yīng)用程序的錯(cuò)誤處理也同樣重要,但真正受到重視,還是最近幾年的事。實(shí)際上,我們

要面對(duì)這樣一個(gè)不爭(zhēng)的事實(shí):使用 Web 的絕大多數(shù)人都不是技術(shù)高手,其中甚至有很多人根本就不明

白瀏覽器到底是什么,更不用說(shuō)讓他們說(shuō)喜歡哪一個(gè)了。本章前面討論過(guò),每個(gè)瀏覽器在發(fā)生 JavaScript

錯(cuò)誤時(shí)的行為都或多或少有一些差異。有的會(huì)顯示小圖標(biāo),有的則什么動(dòng)靜也沒有,瀏覽器對(duì) JavaScript

錯(cuò)誤的這些默認(rèn)行為對(duì)最終用戶而言,毫無(wú)規(guī)律可循。最理想的情況下,用戶遇到錯(cuò)誤搞不清為什么,

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

第518頁(yè)

500 第 17 章 錯(cuò)誤處理與調(diào)試

他們會(huì)再試著重做一次;最糟糕的情況下,用戶會(huì)惱羞成怒,一去不復(fù)返了。良好的錯(cuò)誤處理機(jī)制可以

讓用戶及時(shí)得到提醒,知道到底發(fā)生了什么事,因而不會(huì)驚惶失措。為此,作為開發(fā)人員,我們必須理

解在處理 JavaScript 錯(cuò)誤的時(shí)候,都有哪些手段和工具可以利用。

17.2.1 try-catch語(yǔ)句

ECMA-262 第 3 版引入了 try-catch 語(yǔ)句,作為 JavaScript 中處理異常的一種標(biāo)準(zhǔn)方式?;镜恼Z(yǔ)

法如下所示,顯而易見,這與 Java 中的 try-catch 語(yǔ)句是完全相同的。

try{

// 可能會(huì)導(dǎo)致錯(cuò)誤的代碼

} catch(error){

// 在錯(cuò)誤發(fā)生時(shí)怎么處理

}

也就是說(shuō),我們應(yīng)該把所有可能會(huì)拋出錯(cuò)誤的代碼都放在 try 語(yǔ)句塊中,而把那些用于錯(cuò)誤處理的

代碼放在 catch 塊中。例如:

try {

window.someNonexistentFunction();

} catch (error){

alert(\"An error happened!\");

}

如果 try 塊中的任何代碼發(fā)生了錯(cuò)誤,就會(huì)立即退出代碼執(zhí)行過(guò)程,然后接著執(zhí)行 catch 塊。此

時(shí),catch 塊會(huì)接收到一個(gè)包含錯(cuò)誤信息的對(duì)象。與在其他語(yǔ)言中不同的是,即使你不想使用這個(gè)錯(cuò)誤

對(duì)象,也要給它起個(gè)名字。這個(gè)對(duì)象中包含的實(shí)際信息會(huì)因?yàn)g覽器而異,但共同的是有一個(gè)保存著錯(cuò)誤

消息的 message 屬性。ECMA-262 還規(guī)定了一個(gè)保存錯(cuò)誤類型的 name 屬性;當(dāng)前所有瀏覽器都支持這

個(gè)屬性(Opera 9 之前的版本不支持這個(gè)屬性)。因此,在發(fā)生錯(cuò)誤時(shí),就可以像下面這樣實(shí)事求是地顯

示瀏覽器給出的消息。

try {

window.someNonexistentFunction();

} catch (error){

alert(error.message);

}

TryCatchExample01.htm

這個(gè)例子在向用戶顯示錯(cuò)誤消息時(shí),使用了錯(cuò)誤對(duì)象的 message 屬性。這個(gè) message 屬性是唯一

一個(gè)能夠保證所有瀏覽器都支持的屬性,除此之外,IE、Firefox、Safari、Chrome 以及 Opera 都為事件

對(duì)象添加了其他相關(guān)信息。IE 添加了與 message 屬性完全相同的 description 屬性,還添加了保存

著內(nèi)部錯(cuò)誤數(shù)量的 number 屬性。Firefox 添加了 fileName、lineNumber 和 stack(包含棧跟蹤信息)

屬性。Safari 添加了 line(表示行號(hào))、sourceId(表示內(nèi)部錯(cuò)誤代碼)和 sourceURL 屬性。當(dāng)然,

在跨瀏覽器編程時(shí),最好還是只使用 message 屬性。

1. finally 子句

雖然在 try-catch 語(yǔ)句中是可選的,但 finally 子句一經(jīng)使用,其代碼無(wú)論如何都會(huì)執(zhí)行。換句

話說(shuō),try 語(yǔ)句塊中的代碼全部正常執(zhí)行,finally 子句會(huì)執(zhí)行;如果因?yàn)槌鲥e(cuò)而執(zhí)行了 catch 語(yǔ)句

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

第519頁(yè)

17.2 錯(cuò)誤處理 501

14

2

3

17

5

13

6

7

8

9

10

11

12

塊,finally 子句照樣還會(huì)執(zhí)行。只要代碼中包含 finally 子句,則無(wú)論 try 或 catch 語(yǔ)句塊中包

含什么代碼——甚至 return 語(yǔ)句,都不會(huì)阻止 finally 子句的執(zhí)行。來(lái)看下面這個(gè)函數(shù)。

function testFinally(){

try {

return 2;

} catch (error){

return 1;

} finally {

return 0;

}

}

TryCatchExample02.htm

這個(gè)函數(shù)在 try-catch 語(yǔ)句的每一部分都放了一條 return 語(yǔ)句。表面上看,調(diào)用這個(gè)函數(shù)會(huì)返

回 2,因?yàn)榉祷?2 的 return 語(yǔ)句位于 try 語(yǔ)句塊中,而執(zhí)行該語(yǔ)句又不會(huì)出錯(cuò)??墒?,由于最后還有

一個(gè) finally 子句,結(jié)果就會(huì)導(dǎo)致該 return 語(yǔ)句被忽略;也就是說(shuō),調(diào)用這個(gè)函數(shù)只能返回 0。如果

把 finally 子句拿掉,這個(gè)函數(shù)將返回 2。

如果提供 finally 子句,則 catch 子句就成了可選的(catch 或 finally 有一個(gè)即可)。IE7 及

更早版本中有一個(gè) bug:除非有 catch 子句,否則 finally 中的代碼永遠(yuǎn)不會(huì)執(zhí)行。如果你仍然要考

慮 IE 的早期版本,那就只好提供一個(gè) catch 子句,哪怕里面什么都不寫。IE8 修復(fù)了這個(gè) bug。

請(qǐng)讀者務(wù)必要記住,只要代碼中包含finally 子句,那么無(wú)論try 還是catch 語(yǔ)句塊

中的return 語(yǔ)句都將被忽略。因此,在使用finally 子句之前,一定要非常清楚你想讓代

碼怎么樣。

2. 錯(cuò)誤類型

執(zhí)行代碼期間可能會(huì)發(fā)生的錯(cuò)誤有多種類型。每種錯(cuò)誤都有對(duì)應(yīng)的錯(cuò)誤類型,而當(dāng)錯(cuò)誤發(fā)生時(shí),就

會(huì)拋出相應(yīng)類型的錯(cuò)誤對(duì)象。ECMA-262 定義了下列 7 種錯(cuò)誤類型:

? Error

? EvalError

? RangeError

? ReferenceError

? SyntaxError

? TypeError

? URIError

其中,Error 是基類型,其他錯(cuò)誤類型都繼承自該類型。因此,所有錯(cuò)誤類型共享了一組相同的屬

性(錯(cuò)誤對(duì)象中的方法全是默認(rèn)的對(duì)象方法)。Error 類型的錯(cuò)誤很少見,如果有也是瀏覽器拋出的;

這個(gè)基類型的主要目的是供開發(fā)人員拋出自定義錯(cuò)誤。

EvalError 類型的錯(cuò)誤會(huì)在使用 eval()函數(shù)而發(fā)生異常時(shí)被拋出。ECMA-262 中對(duì)這個(gè)錯(cuò)誤有如

下描述:“如果以非直接調(diào)用的方式使用 eval 屬性的值(換句話說(shuō),沒有明確地將其名稱作為一個(gè)

Identifier,即用作 CallExpression 中的 MemberExpression),或者為 eval 屬性賦值。”簡(jiǎn)單

地說(shuō),如果沒有把 eval()當(dāng)成函數(shù)調(diào)用,就會(huì)拋出錯(cuò)誤,例如:

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

第520頁(yè)

502 第 17 章 錯(cuò)誤處理與調(diào)試

new eval(); //拋出 EvalError

eval = foo; //拋出 EvalError

在實(shí)踐中,瀏覽器不一定會(huì)在應(yīng)該拋出錯(cuò)誤時(shí)就拋出 EvalError。例如,F(xiàn)irefox 4+和 IE8 對(duì)第一

種情況會(huì)拋出 TypeError,而第二種情況會(huì)成功執(zhí)行,不發(fā)生錯(cuò)誤。有鑒于此,加上在實(shí)際開發(fā)中極

少會(huì)這樣使用 eval(),所以遇到這種錯(cuò)誤類型的可能性極小。

RangeError 類型的錯(cuò)誤會(huì)在數(shù)值超出相應(yīng)范圍時(shí)觸發(fā)。例如,在定義數(shù)組時(shí),如果指定了數(shù)組不

支持的項(xiàng)數(shù)(如?20 或 Number.MAX_VALUE),就會(huì)觸發(fā)這種錯(cuò)誤。下面是具體的例子。

var items1 = new Array(-20); //拋出 RangeError

var items2 = new Array(Number.MAX_VALUE); //拋出 RangeError

JavaScript 中經(jīng)常會(huì)出現(xiàn)這種范圍錯(cuò)誤。

在找不到對(duì)象的情況下,會(huì)發(fā)生 ReferenceError(這種情況下,會(huì)直接導(dǎo)致人所共知的\"object

expected\"瀏覽器錯(cuò)誤)。通常,在訪問(wèn)不存在的變量時(shí),就會(huì)發(fā)生這種錯(cuò)誤,例如:

var obj = x; //在 x 并未聲明的情況下拋出 ReferenceError

至于 SyntaxError,當(dāng)我們把語(yǔ)法錯(cuò)誤的 JavaScript 字符串傳入 eval()函數(shù)時(shí),就會(huì)導(dǎo)致此類錯(cuò)

誤。例如:

eval(\"a ++ b\"); //拋出 SyntaxError

如果語(yǔ)法錯(cuò)誤的代碼出現(xiàn)在 eval()函數(shù)之外,則不太可能使用 SyntaxError,因?yàn)榇藭r(shí)的語(yǔ)法錯(cuò)

誤會(huì)導(dǎo)致 JavaScript 代碼立即停止執(zhí)行。

TypeError 類型在 JavaScript 中會(huì)經(jīng)常用到,在變量中保存著意外的類型時(shí),或者在訪問(wèn)不存在的

方法時(shí),都會(huì)導(dǎo)致這種錯(cuò)誤。錯(cuò)誤的原因雖然多種多樣,但歸根結(jié)底還是由于在執(zhí)行特定于類型的操作

時(shí),變量的類型并不符合要求所致。下面來(lái)看幾個(gè)例子。

var o = new 10; //拋出 TypeError

alert(\"name\" in true); //拋出 TypeError

Function.prototype.toString.call(\"name\"); //拋出 TypeError

最常發(fā)生類型錯(cuò)誤的情況,就是傳遞給函數(shù)的參數(shù)事先未經(jīng)檢查,結(jié)果傳入類型與預(yù)期類型不相符。

在使用 encodeURI()或 decodeURI(),而 URI 格式不正確時(shí),就會(huì)導(dǎo)致 URIError 錯(cuò)誤。這種

錯(cuò)誤也很少見,因?yàn)榍懊嬲f(shuō)的這兩個(gè)函數(shù)的容錯(cuò)性非常高。

利用不同的錯(cuò)誤類型,可以獲悉更多有關(guān)異常的信息,從而有助于對(duì)錯(cuò)誤作出恰當(dāng)?shù)奶幚?。要想?/p>

道錯(cuò)誤的類型,可以像下面這樣在 try-catch 語(yǔ)句的 catch 語(yǔ)句中使用 instanceof 操作符。

try {

someFunction();

} catch (error){

if (error instanceof TypeError){

//處理類型錯(cuò)誤

} else if (error instanceof ReferenceError){

//處理引用錯(cuò)誤

} else {

//處理其他類型的錯(cuò)誤

}

}

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

第521頁(yè)

17.2 錯(cuò)誤處理 503

14

2

3

17

5

13

6

7

8

9

10

11

12

在跨瀏覽器編程中,檢查錯(cuò)誤類型是確定處理方式的最簡(jiǎn)便途徑;包含在 message 屬性中的錯(cuò)誤

消息會(huì)因?yàn)g覽器而異。

3. 合理使用 try-catch

當(dāng) try-catch 語(yǔ)句中發(fā)生錯(cuò)誤時(shí),瀏覽器會(huì)認(rèn)為錯(cuò)誤已經(jīng)被處理了,因而不會(huì)通過(guò)本章前面討論

的機(jī)制記錄或報(bào)告錯(cuò)誤。對(duì)于那些不要求用戶懂技術(shù),也不需要用戶理解錯(cuò)誤的 Web 應(yīng)用程序,這應(yīng)

該說(shuō)是個(gè)理想的結(jié)果。不過(guò),try-catch 能夠讓我們實(shí)現(xiàn)自己的錯(cuò)誤處理機(jī)制。

使用 try-catch 最適合處理那些我們無(wú)法控制的錯(cuò)誤。假設(shè)你在使用一個(gè)大型 JavaScript 庫(kù)中的

函數(shù),該函數(shù)可能會(huì)有意無(wú)意地拋出一些錯(cuò)誤。由于我們不能修改這個(gè)庫(kù)的源代碼,所以大可將對(duì)該函

數(shù)的調(diào)用放在 try-catch 語(yǔ)句當(dāng)中,萬(wàn)一有什么錯(cuò)誤發(fā)生,也好恰當(dāng)?shù)靥幚硭鼈儭?/p>

在明明白白地知道自己的代碼會(huì)發(fā)生錯(cuò)誤時(shí),再使用 try-catch 語(yǔ)句就不太合適了。例如,如果

傳遞給函數(shù)的參數(shù)是字符串而非數(shù)值,就會(huì)造成函數(shù)出錯(cuò),那么就應(yīng)該先檢查參數(shù)的類型,然后再?zèng)Q定

如何去做。在這種情況下,不應(yīng)用使用 try-catch 語(yǔ)句。

17.2.2 拋出錯(cuò)誤

與 try-catch 語(yǔ)句相配的還有一個(gè) throw 操作符,用于隨時(shí)拋出自定義錯(cuò)誤。拋出錯(cuò)誤時(shí),必須

要給 throw 操作符指定一個(gè)值,這個(gè)值是什么類型,沒有要求。下列代碼都是有效的。

throw 12345;

throw \"Hello world!\";

throw true;

throw { name: \"JavaScript\"};

在遇到 throw 操作符時(shí),代碼會(huì)立即停止執(zhí)行。僅當(dāng)有 try-catch 語(yǔ)句捕獲到被拋出的值時(shí),代

碼才會(huì)繼續(xù)執(zhí)行。

通過(guò)使用某種內(nèi)置錯(cuò)誤類型,可以更真實(shí)地模擬瀏覽器錯(cuò)誤。每種錯(cuò)誤類型的構(gòu)造函數(shù)接收一個(gè)參

數(shù),即實(shí)際的錯(cuò)誤消息。下面是一個(gè)例子。

throw new Error(\"Something bad happened.\");

這行代碼拋出了一個(gè)通用錯(cuò)誤,帶有一條自定義錯(cuò)誤消息。瀏覽器會(huì)像處理自己生成的錯(cuò)誤一樣,

來(lái)處理這行代碼拋出的錯(cuò)誤。換句話說(shuō),瀏覽器會(huì)以常規(guī)方式報(bào)告這一錯(cuò)誤,并且會(huì)顯示這里的自定義

錯(cuò)誤消息。像下面使用其他錯(cuò)誤類型,也可以模擬出類似的瀏覽器錯(cuò)誤。

throw new SyntaxError(\"I don’t like your syntax.\");

throw new TypeError(\"What type of variable do you take me for?\");

throw new RangeError(\"Sorry, you just don’t have the range.\");

throw new EvalError(\"That doesn’t evaluate.\");

throw new URIError(\"Uri, is that you?\");

throw new ReferenceError(\"You didn’t cite your references properly.\");

在創(chuàng)建自定義錯(cuò)誤消息時(shí)最常用的錯(cuò)誤類型是 Error、RangeError、ReferenceError 和 TypeError。

另外,利用原型鏈還可以通過(guò)繼承 Error 來(lái)創(chuàng)建自定義錯(cuò)誤類型(原型鏈在第 6 章中介紹)。此時(shí),

需要為新創(chuàng)建的錯(cuò)誤類型指定 name 和 message 屬性。來(lái)看一個(gè)例子。

function CustomError(message){

this.name = \"CustomError\";

this.message = message;

}

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

第522頁(yè)

504 第 17 章 錯(cuò)誤處理與調(diào)試

CustomError.prototype = new Error();

throw new CustomError(\"My message\");

ThrowingErrorsExample01.htm

瀏覽器對(duì)待繼承自 Error 的自定義錯(cuò)誤類型,就像對(duì)待其他錯(cuò)誤類型一樣。如果要捕獲自己拋出

的錯(cuò)誤并且把它與瀏覽器錯(cuò)誤區(qū)別對(duì)待的話,創(chuàng)建自定義錯(cuò)誤是很有用的。

IE 只有在拋出 Error 對(duì)象的時(shí)候才會(huì)顯示自定義錯(cuò)誤消息。對(duì)于其他類型,它

都無(wú)一例外地顯示\"exception thrown and not caught\"(拋出了異常,且未被

捕獲)。

1. 拋出錯(cuò)誤的時(shí)機(jī)

要針對(duì)函數(shù)為什么會(huì)執(zhí)行失敗給出更多信息,拋出自定義錯(cuò)誤是一種很方便的方式。應(yīng)該在出現(xiàn)某

種特定的已知錯(cuò)誤條件,導(dǎo)致函數(shù)無(wú)法正常執(zhí)行時(shí)拋出錯(cuò)誤。換句話說(shuō),瀏覽器會(huì)在某種特定的條件下

執(zhí)行函數(shù)時(shí)拋出錯(cuò)誤。例如,下面的函數(shù)會(huì)在參數(shù)不是數(shù)組的情況下失敗。

function process(values){

values.sort();

for (var i=0, len=values.length; i < len; i++){

if (values[i] > 100){

return values[i];

}

}

return -1;

}

ThrowingErrorsExample02.htm

如果執(zhí)行這個(gè)函數(shù)時(shí)傳給它一個(gè)字符串參數(shù),那么對(duì) sort()的調(diào)用就會(huì)失敗。對(duì)此,不同瀏覽器

會(huì)給出不同的錯(cuò)誤消息,但都不是特別明確,如下所示。

? IE:屬性或方法不存在。

? Firefox:values.sort()不是函數(shù)。

? Safari:值 undefined(表達(dá)式 values.sort 的結(jié)果)不是對(duì)象。

? Chrome:對(duì)象名沒有方法'sort'。

? Opera:類型不匹配(通常是在需要對(duì)象的地方使用了非對(duì)象值)。

盡管 Firefox、Chrome 和 Safari 都明確指出了代碼中導(dǎo)致錯(cuò)誤的部分,但錯(cuò)誤消息并沒有清楚地告

訴我們到底出了什么問(wèn)題,該怎么修復(fù)問(wèn)題。在處理類似前面例子中的那個(gè)函數(shù)時(shí),通過(guò)調(diào)試處理這些

錯(cuò)誤消息沒有什么困難。但是,在面對(duì)包含數(shù)千行 JavaScript 代碼的復(fù)雜的 Web 應(yīng)用程序時(shí),要想查找

錯(cuò)誤來(lái)源就沒有那么容易了。這種情況下,帶有適當(dāng)信息的自定義錯(cuò)誤能夠顯著提升代碼的可維護(hù)性。

來(lái)看下面的例子。

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

第523頁(yè)

17.2 錯(cuò)誤處理 505

14

2

3

17

5

13

6

7

8

9

10

11

12

function process(values){

if (!(values instanceof Array)){

throw new Error(\"process(): Argument must be an array.\");

}

values.sort();

for (var i=0, len=values.length; i < len; i++){

if (values[i] > 100){

return values[i];

}

}

return -1;

}

ThrowingErrorsExample02.htm

在重寫后的這個(gè)函數(shù)中,如果 values 參數(shù)不是數(shù)組,就會(huì)拋出一個(gè)錯(cuò)誤。錯(cuò)誤消息中包含了函數(shù)

的名稱,以及為什么會(huì)發(fā)生錯(cuò)誤的明確描述。如果一個(gè)復(fù)雜的 Web 應(yīng)用程序發(fā)生了這個(gè)錯(cuò)誤,那么查

找問(wèn)題的根源也就容易多了。

建議讀者在開發(fā) JavaScript 代碼的過(guò)程中,重點(diǎn)關(guān)注函數(shù)和可能導(dǎo)致函數(shù)執(zhí)行失敗的因素。良好的

錯(cuò)誤處理機(jī)制應(yīng)該可以確保代碼中只發(fā)生你自己拋出的錯(cuò)誤。

在多框架環(huán)境下使用 instanceof 來(lái)檢測(cè)數(shù)組有一些問(wèn)題。詳細(xì)內(nèi)容請(qǐng)參考

22.1.1 節(jié)。

2. 拋出錯(cuò)誤與使用 try-catch

關(guān)于何時(shí)該拋出錯(cuò)誤,而何時(shí)該使用 try-catch 來(lái)捕獲它們,是一個(gè)老生常談的問(wèn)題。一般來(lái)說(shuō),

應(yīng)用程序架構(gòu)的較低層次中經(jīng)常會(huì)拋出錯(cuò)誤,但這個(gè)層次并不會(huì)影響當(dāng)前執(zhí)行的代碼,因而錯(cuò)誤通常得

不到真正的處理。如果你打算編寫一個(gè)要在很多應(yīng)用程序中使用的 JavaScript 庫(kù),甚至只編寫一個(gè)可能

會(huì)在應(yīng)用程序內(nèi)部多個(gè)地方使用的輔助函數(shù),我都強(qiáng)烈建議你在拋出錯(cuò)誤時(shí)提供詳盡的信息。然后,即

可在應(yīng)用程序中捕獲并適當(dāng)?shù)靥幚磉@些錯(cuò)誤。

說(shuō)到拋出錯(cuò)誤與捕獲錯(cuò)誤,我們認(rèn)為只應(yīng)該捕獲那些你確切地知道該如何處理的錯(cuò)誤。捕獲錯(cuò)誤的

目的在于避免瀏覽器以默認(rèn)方式處理它們;而拋出錯(cuò)誤的目的在于提供錯(cuò)誤發(fā)生具體原因的消息。

17.2.3 錯(cuò)誤(error)事件

任何沒有通過(guò) try-catch 處理的錯(cuò)誤都會(huì)觸發(fā) window 對(duì)象的 error 事件。這個(gè)事件是 Web 瀏覽

器最早支持的事件之一,IE、Firefox 和 Chrome 為保持向后兼容,并沒有對(duì)這個(gè)事件作任何修改(Opera

和 Safari 不支持 error 事件)。在任何 Web 瀏覽器中,onerror 事件處理程序都不會(huì)創(chuàng)建 event 對(duì)象,

但它可以接收三個(gè)參數(shù):錯(cuò)誤消息、錯(cuò)誤所在的 URL 和行號(hào)。多數(shù)情況下,只有錯(cuò)誤消息有用,因?yàn)?/p>

URL 只是給出了文檔的位置,而行號(hào)所指的代碼行既可能出自嵌入的 JavaScript 代碼,也可能出自外部

的文件。要指定 onerror 事件處理程序,必須使用如下所示的 DOM0 級(jí)技術(shù),它沒有遵循“DOM2 級(jí)

事件”的標(biāo)準(zhǔn)格式。

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

第524頁(yè)

506 第 17 章 錯(cuò)誤處理與調(diào)試

window.onerror = function(message, url, line){

alert(message);

};

只要發(fā)生錯(cuò)誤,無(wú)論是不是瀏覽器生成的,都會(huì)觸發(fā) error 事件,并執(zhí)行這個(gè)事件處理程序。然

后,瀏覽器默認(rèn)的機(jī)制發(fā)揮作用,像往常一樣顯示出錯(cuò)誤消息。像下面這樣在事件處理程序中返回

false,可以阻止瀏覽器報(bào)告錯(cuò)誤的默認(rèn)行為。

window.onerror = function(message, url, line){

alert(message);

return false;

};

OnErrorExample01.htm

通過(guò)返回 false,這個(gè)函數(shù)實(shí)際上就充當(dāng)了整個(gè)文檔中的 try-catch 語(yǔ)句,可以捕獲所有無(wú)代碼

處理的運(yùn)行時(shí)錯(cuò)誤。這個(gè)事件處理程序是避免瀏覽器報(bào)告錯(cuò)誤的最后一道防線,理想情況下,只要可能

就不應(yīng)該使用它。只要能夠適當(dāng)?shù)厥褂?try-catch 語(yǔ)句,就不會(huì)有錯(cuò)誤交給瀏覽器,也就不會(huì)觸發(fā)

error 事件。

瀏覽器在使用這個(gè)事件處理錯(cuò)誤時(shí)的方式有明顯不同。在 IE 中,即使發(fā)生 error

事件,代碼仍然會(huì)正常執(zhí)行;所有變量和數(shù)據(jù)都將得到保留,因此能在 onerror 事

件處理程序中訪問(wèn)它們。但在 Firefox 中,常規(guī)代碼會(huì)停止執(zhí)行,事件發(fā)生之前的所

有變量和數(shù)據(jù)都將被銷毀,因此幾乎就無(wú)法判斷錯(cuò)誤了。

圖像也支持 error 事件。只要圖像的 src 特性中的 URL 不能返回可以被識(shí)別的圖像格式,就會(huì)觸

發(fā) error 事件。此時(shí)的 error 事件遵循 DOM 格式,會(huì)返回一個(gè)以圖像為目標(biāo)的 event 對(duì)象。下面是

一個(gè)例子。

var image = new Image();

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

alert(\"Image loaded!\");

});

EventUtil.addHandler(image, \"error\", function(event){

alert(\"Image not loaded!\");

});

image.src = \"smilex.gif\"; //指定不存在的文件

OnErrorExample02.htm

在這個(gè)例子中,當(dāng)加載圖像失敗時(shí)就會(huì)顯示一個(gè)警告框。需要注意的是,發(fā)生 error 事件時(shí),圖

像下載過(guò)程已經(jīng)結(jié)束,也就是說(shuō)不能再重新下載了。

17.2.4 處理錯(cuò)誤的策略

過(guò)去,所謂 Web 應(yīng)用程序的錯(cuò)誤處理策略僅限于服務(wù)器端。在談到錯(cuò)誤與錯(cuò)誤處理時(shí),通常要考

慮很多方面,涉及一些工具,例如記錄和監(jiān)控系統(tǒng)。這些工具的用途在于分析錯(cuò)誤模式,追查錯(cuò)誤原因,

同時(shí)幫助確定錯(cuò)誤會(huì)影響到多少用戶。

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

第525頁(yè)

17.2 錯(cuò)誤處理 507

14

2

3

17

5

13

6

7

8

9

10

11

12

在 Web 應(yīng)用程序的 JavaScript 這一端,錯(cuò)誤處理策略也同樣重要。由于任何 JavaScript 錯(cuò)誤都可能

導(dǎo)致網(wǎng)頁(yè)無(wú)法使用,因此搞清楚何時(shí)以及為什么發(fā)生錯(cuò)誤至關(guān)重要。絕大多數(shù) Web 應(yīng)用程序的用戶都

不懂技術(shù),遇到錯(cuò)誤時(shí)很容易心煩意亂。有時(shí)候,他們可能會(huì)刷新頁(yè)面以期解決問(wèn)題,而有時(shí)候則會(huì)放

棄努力。作為開發(fā)人員,必須要知道代碼何時(shí)可能出錯(cuò),會(huì)出什么錯(cuò),同時(shí)還要有一個(gè)跟蹤此類問(wèn)題的

系統(tǒng)。

17.2.5 常見的錯(cuò)誤類型

錯(cuò)誤處理的核心,是首先要知道代碼里會(huì)發(fā)生什么錯(cuò)誤。由于 JavaScript 是松散類型的,而且也不

會(huì)驗(yàn)證函數(shù)的參數(shù),因此錯(cuò)誤只會(huì)在代碼運(yùn)行期間出現(xiàn)。一般來(lái)說(shuō),需要關(guān)注三種錯(cuò)誤:

? 類型轉(zhuǎn)換錯(cuò)誤

? 數(shù)據(jù)類型錯(cuò)誤

? 通信錯(cuò)誤

以上錯(cuò)誤分別會(huì)在特定的模式下或者沒有對(duì)值進(jìn)行足夠的檢查的情況下發(fā)生。

1. 類型轉(zhuǎn)換錯(cuò)誤

類型轉(zhuǎn)換錯(cuò)誤發(fā)生在使用某個(gè)操作符,或者使用其他可能會(huì)自動(dòng)轉(zhuǎn)換值的數(shù)據(jù)類型的語(yǔ)言結(jié)構(gòu)時(shí)。

在使用相等(==)和不相等(!=)操作符,或者在 if、for 及 while 等流控制語(yǔ)句中使用非布爾值時(shí),

最常發(fā)生類型轉(zhuǎn)換錯(cuò)誤。

第 3 章討論的相等和不相等操作符在執(zhí)行比較之前會(huì)先轉(zhuǎn)換不同類型的值。由于在非動(dòng)態(tài)語(yǔ)言中,

開發(fā)人員都使用相同的符號(hào)執(zhí)行直觀的比較,因此在 JavaScript 中往往也會(huì)以相同方式錯(cuò)誤地使用它們。

多數(shù)情況下,我們建議使用全等(===)和不全等(!==)操作符,以避免類型轉(zhuǎn)換。來(lái)看一個(gè)例子。

alert(5 == \"5\"); //true

alert(5 === \"5\"); //false

alert(1 == true); //true

alert(1 === true); //false

這里使用了相等和全等操作符比較了數(shù)值 5 和字符串\"5\"。相等操作符首先會(huì)將數(shù)值 5 轉(zhuǎn)換成字符

串\"5\",然后再將其與另一個(gè)字符串\"5\"進(jìn)行比較,結(jié)果是 true。全等操作符知道要比較的是兩種不同

的數(shù)據(jù)類型,因而直接返回 false。對(duì)于 1 和 true 也是如此:相等操作符認(rèn)為它們相等,而全等操作

符認(rèn)為它們不相等。使用全等和非全等操作符,可以避免發(fā)生因?yàn)槭褂孟嗟群筒幌嗟炔僮鞣l(fā)的類型

轉(zhuǎn)換錯(cuò)誤,因此我們強(qiáng)烈推薦使用。

容易發(fā)生類型轉(zhuǎn)換錯(cuò)誤的另一個(gè)地方,就是流控制語(yǔ)句。像 if 之類的語(yǔ)句在確定下一步操作之前,

會(huì)自動(dòng)把任何值轉(zhuǎn)換成布爾值。尤其是 if 語(yǔ)句,如果使用不當(dāng),最容易出錯(cuò)。來(lái)看下面的例子。

function concat(str1, str2, str3){

var result = str1 + str2;

if (str3){ //絕對(duì)不要這樣!!!

result += str3;

}

return result;

}

這個(gè)函數(shù)的用意是拼接兩或三個(gè)字符串,然后返回結(jié)果。其中,第三個(gè)字符串是可選的,因此必須

要檢查。第 3 章曾經(jīng)介紹過(guò),未使用過(guò)的命名變量會(huì)自動(dòng)被賦予 undefined 值。而 undefined 值可以

被轉(zhuǎn)換成布爾值 false,因此這個(gè)函數(shù)中的 if 語(yǔ)句實(shí)際上只適用于提供了第三個(gè)參數(shù)的情況。問(wèn)題在

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

第526頁(yè)

508 第 17 章 錯(cuò)誤處理與調(diào)試

于,并不是只有 undefined 才會(huì)被轉(zhuǎn)換成 false,也不是只有字符串值才可以轉(zhuǎn)換為 true。例如,

假設(shè)第三個(gè)參數(shù)是數(shù)值 0,那么 if 語(yǔ)句的測(cè)試就會(huì)失敗,而對(duì)數(shù)值 1 的測(cè)試則會(huì)通過(guò)。

在流控制語(yǔ)句中使用非布爾值,是極為常見的一個(gè)錯(cuò)誤來(lái)源。為避免此類錯(cuò)誤,就要做到在條件比

較時(shí)切實(shí)傳入布爾值。實(shí)際上,執(zhí)行某種形式的比較就可以達(dá)到這個(gè)目的。例如,我們可以將前面的函

數(shù)重寫如下。

function concat(str1, str2, str3){

var result = str1 + str2;

if (typeof str3 == \"string\"){ //恰當(dāng)?shù)谋容^

result += str3;

}

return result;

}

在這個(gè)重寫后的函數(shù)中,if 語(yǔ)句的條件會(huì)基于比較返回一個(gè)布爾值。這個(gè)函數(shù)相對(duì)可靠得多,不

容易受非正常值的影響。

2. 數(shù)據(jù)類型錯(cuò)誤

JavaScript 是松散類型的,也就是說(shuō),在使用變量和函數(shù)參數(shù)之前,不會(huì)對(duì)它們進(jìn)行比較以確保它

們的數(shù)據(jù)類型正確。為了保證不會(huì)發(fā)生數(shù)據(jù)類型錯(cuò)誤,只能依靠開發(fā)人員編寫適當(dāng)?shù)臄?shù)據(jù)類型檢測(cè)代碼。

在將預(yù)料之外的值傳遞給函數(shù)的情況下,最容易發(fā)生數(shù)據(jù)類型錯(cuò)誤。

在前面的例子中,通過(guò)檢測(cè)第三個(gè)參數(shù)可以確保它是一個(gè)字符串,但是并沒有檢測(cè)另外兩個(gè)參數(shù)。

如果該函數(shù)必須要返回一個(gè)字符串,那么只要給它傳入兩個(gè)數(shù)值,忽略第三個(gè)參數(shù),就可以輕易地導(dǎo)致

它的執(zhí)行結(jié)果錯(cuò)誤。類似的情況也存在于下面這個(gè)函數(shù)中。

//不安全的函數(shù),任何非字符串值都會(huì)導(dǎo)致錯(cuò)誤

function getQueryString(url){

var pos = url.indexOf(\"?\");

if (pos > -1){

return url.substring(pos +1);

}

return \"\";

}

這個(gè)函數(shù)的用意是返回給定 URL 中的查詢字符串。為此,它首先使用 indexOf()尋找字符串中的

問(wèn)號(hào)。如果找到了,利用 substring()方法返回問(wèn)號(hào)后面的所有字符串。這個(gè)例子中的兩個(gè)函數(shù)只能

操作字符串,因此只要傳入其他數(shù)據(jù)類型的值就會(huì)導(dǎo)致錯(cuò)誤。而添加一條簡(jiǎn)單的類型檢測(cè)語(yǔ)句,就可以

確保函數(shù)不那么容易出錯(cuò)。

function getQueryString(url){

if (typeof url == \"string\"){ //通過(guò)檢查類型確保安全

var pos = url.indexOf(\"?\");

if (pos > -1){

return url.substring(pos +1);

}

}

return \"\";

}

重寫后的這個(gè)函數(shù)首先檢查了傳入的值是不是字符串。這樣,就確保了函數(shù)不會(huì)因?yàn)榻邮盏椒亲址?/p>

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

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

第527頁(yè)

17.2 錯(cuò)誤處理 509

14

2

3

17

5

13

6

7

8

9

10

11

12

前一節(jié)提到過(guò),在流控制語(yǔ)句中使用非布爾值作為條件很容易導(dǎo)致類型轉(zhuǎn)換錯(cuò)誤。同樣,這樣做也

經(jīng)常會(huì)導(dǎo)致數(shù)據(jù)類型錯(cuò)誤。來(lái)看下面的例子。

//不安全的函數(shù),任何非數(shù)組值都會(huì)導(dǎo)致錯(cuò)誤

function reverseSort(values){

if (values){ //絕對(duì)不要這樣!!!

values.sort();

values.reverse();

}

}

這個(gè) reverseSort()函數(shù)可以將數(shù)組反向排序,其中用到了 sort()和 reverse()方法。對(duì)于 if

語(yǔ)句中的控制條件而言,任何會(huì)轉(zhuǎn)換為 true 的非數(shù)組值都會(huì)導(dǎo)致錯(cuò)誤。另一個(gè)常見的錯(cuò)誤就是將參數(shù)

與 null 值進(jìn)行比較,如下所示。

//不安全的函數(shù),任何非數(shù)組值都會(huì)導(dǎo)致錯(cuò)誤

function reverseSort(values){

if (values != null){ //絕對(duì)不要這樣!!!

values.sort();

values.reverse();

}

}

與 null 進(jìn)行比較只能確保相應(yīng)的值不是 null 和 undefined(這就相當(dāng)于使用相等和不相等操

作)。要確保傳入的值有效,僅檢測(cè) null 值是不夠的;因此,不應(yīng)該使用這種技術(shù)。同樣,我們也不

推薦將某個(gè)值與 undefined 作比較。

另一種錯(cuò)誤的做法,就是只針對(duì)要使用的某一個(gè)特性執(zhí)行特性檢測(cè)。來(lái)看下面的例子。

//還是不安全,任何非數(shù)組值都會(huì)導(dǎo)致錯(cuò)誤

function reverseSort(values){

if (typeof values.sort == \"function\"){ //絕對(duì)不要這樣!!!

values.sort();

values.reverse();

}

}

在這個(gè)例子中,代碼首先檢測(cè)了參數(shù)中是否存在 sort()方法。這樣,如果傳入一個(gè)包含 sort()

方法的對(duì)象(而不是數(shù)組)當(dāng)然也會(huì)通過(guò)檢測(cè),但在調(diào)用 reverse()函數(shù)時(shí)可能就會(huì)出錯(cuò)了。在確切

知道應(yīng)該傳入什么類型的情況下,最好是使用 instanceof 來(lái)檢測(cè)其數(shù)據(jù)類型,如下所示。

//安全,非數(shù)組值將被忽略

function reverseSort(values){

if (values instanceof Array){ //問(wèn)題解決了

values.sort();

values.reverse();

}

}

最后一個(gè) reverseSort()函數(shù)是安全的:它檢測(cè)了 values,以確保這個(gè)參數(shù)是 Array 類型的實(shí)

例。這樣一來(lái),就可以保證函數(shù)忽略任何非數(shù)組值。

大體上來(lái)說(shuō),基本類型的值應(yīng)該使用 typeof 來(lái)檢測(cè),而對(duì)象的值則應(yīng)該使用 instanceof 來(lái)檢測(cè)。

根據(jù)使用函數(shù)的方式,有時(shí)候并不需要逐個(gè)檢測(cè)所有參數(shù)的數(shù)據(jù)類型。但是,面向公眾的 API 則必須無(wú)

條件地執(zhí)行類型檢查,以確保函數(shù)始終能夠正常地執(zhí)行。

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

第528頁(yè)

510 第 17 章 錯(cuò)誤處理與調(diào)試

3. 通信錯(cuò)誤

隨著 Ajax 編程的興起(第 21 章討論 Ajax),Web 應(yīng)用程序在其生命周期內(nèi)動(dòng)態(tài)加載信息或功能,

已經(jīng)成為一件司空見慣的事。不過(guò),JavaScript 與服務(wù)器之間的任何一次通信,都有可能會(huì)產(chǎn)生錯(cuò)誤。

第一種通信錯(cuò)誤與格式不正確的 URL 或發(fā)送的數(shù)據(jù)有關(guān)。最常見的問(wèn)題是在將數(shù)據(jù)發(fā)送給服務(wù)器

之前,沒有使用 encodeURIComponent()對(duì)數(shù)據(jù)進(jìn)行編碼。例如,下面這個(gè) URL 的格式就是不正確的:

http://www.yourdomain.com/?redir=http://www.someotherdomain.com?a=b&c=d

針對(duì)\"redir=\"后面的所有字符串調(diào)用 encodeURIComponent()就可以解決這個(gè)問(wèn)題,結(jié)果將產(chǎn)生

如下字符串:

http://www.yourdomain.com/?redir=http%3A%2F%2Fwww.someotherdomain.com%3Fa%3Db%26c%3Dd

對(duì)于查詢字符串,應(yīng)該記住必須要使用 encodeURIComponent()方法。為了確保這一點(diǎn),有時(shí)候

可以定義一個(gè)處理查詢字符串的函數(shù),例如:

function addQueryStringArg(url, name, value){

if (url.indexOf(\"?\") == -1){

url += \"?\";

} else {

url += \"&\";

}

url += encodeURIComponent(name) + \"=\" + encodeURIComponent(value);

return url;

}

這個(gè)函數(shù)接收三個(gè)參數(shù):要追加查詢字符串的 URL、參數(shù)名和參數(shù)值。如果傳入的 URL 不包含問(wèn)

號(hào),還要給它添加問(wèn)號(hào);否則,就要添加一個(gè)和號(hào),因?yàn)橛袉?wèn)號(hào)就意味著有其他查詢字符串。然后,再

將經(jīng)過(guò)編碼的查詢字符串的名和值添加到 URL 后面??梢韵裣旅孢@樣使用這個(gè)函數(shù):

var url = \"http://www.somedomain.com\";

var newUrl = addQueryStringArg(url, \"redir\",

\"http://www.someotherdomain.com?a=b&c=d\");

alert(newUrl);

使用這個(gè)函數(shù)而不是手工構(gòu)建 URL,可以確保編碼正確并避免相關(guān)錯(cuò)誤。

另外,在服務(wù)器響應(yīng)的數(shù)據(jù)不正確時(shí),也會(huì)發(fā)生通信錯(cuò)誤。第 10 章曾經(jīng)討論過(guò)動(dòng)態(tài)加載腳本和動(dòng)

態(tài)加載樣式,運(yùn)用這兩種技術(shù)都有可能遇到資源不可用的情況。在沒有返回相應(yīng)資源的情況下,F(xiàn)irefox、

Chrome 和 Safari 會(huì)默默地失敗,IE 和 Opera 則都會(huì)報(bào)錯(cuò)。然而,對(duì)于使用這兩種技術(shù)產(chǎn)生的錯(cuò)誤,很

難判斷和處理。在某些情況下,使用 Ajax 通信可以提供有關(guān)錯(cuò)誤狀態(tài)的更多信息。

在使用 Ajax 通信的情況下,也可能會(huì)發(fā)生通信錯(cuò)誤。相關(guān)的問(wèn)題和錯(cuò)誤將在第

21 章討論。

17.2.6 區(qū)分致命錯(cuò)誤和非致命錯(cuò)誤

任何錯(cuò)誤處理策略中最重要的一個(gè)部分,就是確定錯(cuò)誤是否致命。對(duì)于非致命錯(cuò)誤,可以根據(jù)下列

一或多個(gè)條件來(lái)確定:

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

第529頁(yè)

17.2 錯(cuò)誤處理 511

14

2

3

17

5

13

6

7

8

9

10

11

12

? 不影響用戶的主要任務(wù);

? 只影響頁(yè)面的一部分;

? 可以恢復(fù);

? 重復(fù)相同操作可以消除錯(cuò)誤。

本質(zhì)上,非致命錯(cuò)誤并不是需要關(guān)注的問(wèn)題。例如,Yahoo! Mail(http://mail.yahoo.com)有一項(xiàng)功

能,允許用戶在其界面上發(fā)送手機(jī)短信。如果由于某種原因,發(fā)不了手機(jī)短信了,那也不算是致命錯(cuò)誤,

因?yàn)椴⒉皇菓?yīng)用程序的主要功能有問(wèn)題。用戶使用 Yahoo! Mail 主要是為了查收和撰寫電子郵件。只在

這個(gè)主要功能正常,就沒有理由打斷用戶。沒有必要因?yàn)榘l(fā)生了非致命錯(cuò)誤而對(duì)用戶給出提示——可以

把頁(yè)面中受到影響的區(qū)域替換掉,比如替換成說(shuō)明相應(yīng)功能無(wú)法使用的消息。但是,如果因此打斷用戶,

那確實(shí)沒有必要。

致命錯(cuò)誤,可以通過(guò)以下一或多個(gè)條件來(lái)確定:

? 應(yīng)用程序根本無(wú)法繼續(xù)運(yùn)行;

? 錯(cuò)誤明顯影響到了用戶的主要操作;

? 會(huì)導(dǎo)致其他連帶錯(cuò)誤。

要想采取適當(dāng)?shù)拇胧?,必須要知?JavaScript 在什么情況下會(huì)發(fā)生致命錯(cuò)誤。在發(fā)生致命錯(cuò)誤時(shí),

應(yīng)該立即給用戶發(fā)送一條消息,告訴他們無(wú)法再繼續(xù)手頭的事情了。假如必須刷新頁(yè)面才能讓應(yīng)用程序

正常運(yùn)行,就必須通知用戶,同時(shí)給用戶提供一個(gè)點(diǎn)擊即可刷新頁(yè)面的按鈕。

區(qū)分非致命錯(cuò)誤和致命錯(cuò)誤的主要依據(jù),就是看它們對(duì)用戶的影響。設(shè)計(jì)良好的代碼,可以做到應(yīng)

用程序某一部分發(fā)生錯(cuò)誤不會(huì)不必要地影響另一個(gè)實(shí)際上毫不相干的部分。例如,My Yahoo!

(http://my.yahoo.com)的個(gè)性化主頁(yè)上包含了很多互不依賴的模塊。如果每個(gè)模塊都需要通過(guò) JavaScript

調(diào)用來(lái)初始化,那么你可能會(huì)看到類似下面這樣的代碼:

for (var i=0, len=mods.length; i < len; i++){

mods[i].init(); //可能會(huì)導(dǎo)致致命錯(cuò)誤

}

表面上看,這些代碼沒什么問(wèn)題:依次對(duì)每個(gè)模塊調(diào)用 init()方法。問(wèn)題在于,任何模塊的 init()

方法如果出錯(cuò),都會(huì)導(dǎo)致數(shù)組中后續(xù)的所有模塊無(wú)法再進(jìn)行初始化。從邏輯上說(shuō),這樣編寫代碼沒有什

么意義。畢竟,每個(gè)模塊相互之間沒有依賴關(guān)系,各自實(shí)現(xiàn)不同功能??赡軙?huì)導(dǎo)致致命錯(cuò)誤的原因是代

碼的結(jié)構(gòu)。不過(guò),經(jīng)過(guò)下面這樣修改,就可以把所有模塊的錯(cuò)誤變成非致命的:

for (var i=0, len=mods.length; i < len; i++){

try {

mods[i].init();

} catch (ex) {

//在這里處理錯(cuò)誤

}

}

通過(guò)在 for 循環(huán)中添加 try-catch 語(yǔ)句,任何模塊初始化時(shí)出錯(cuò),都不會(huì)影響其他模塊的初始化。

在以上重寫的代碼中,如果有錯(cuò)誤發(fā)生,相應(yīng)的錯(cuò)誤將會(huì)得到獨(dú)立的處理,并不會(huì)影響到用戶的體驗(yàn)。

17.2.7 把錯(cuò)誤記錄到服務(wù)器

開發(fā) Web 應(yīng)用程序過(guò)程中的一種常見的做法,就是集中保存錯(cuò)誤日志,以便查找重要錯(cuò)誤的原因。

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

第530頁(yè)

512 第 17 章 錯(cuò)誤處理與調(diào)試

例如數(shù)據(jù)庫(kù)和服務(wù)器錯(cuò)誤都會(huì)定期寫入日志,而且會(huì)按照常用 API 進(jìn)行分類。在復(fù)雜的 Web 應(yīng)用程序

中,我們同樣推薦你把 JavaScript 錯(cuò)誤也回寫到服務(wù)器。換句話說(shuō),也要將這些錯(cuò)誤寫入到保存服務(wù)器

端錯(cuò)誤的地方,只不過(guò)要標(biāo)明它們來(lái)自前端。把前后端的錯(cuò)誤集中起來(lái),能夠極大地方便對(duì)數(shù)據(jù)的分析。

要建立這樣一種 JavaScript 錯(cuò)誤記錄系統(tǒng),首先需要在服務(wù)器上創(chuàng)建一個(gè)頁(yè)面(或者一個(gè)服務(wù)器入

口點(diǎn)),用于處理錯(cuò)誤數(shù)據(jù)。這個(gè)頁(yè)面的作用無(wú)非就是從查詢字符串中取得數(shù)據(jù),然后再將數(shù)據(jù)寫入錯(cuò)

誤日志中。這個(gè)頁(yè)面可能會(huì)使用如下所示的函數(shù):

function logError(sev, msg){

var img = new Image();

img.src = \"log.php?sev=\" + encodeURIComponent(sev) + \"&msg=\" +

encodeURIComponent(msg);

}

這個(gè) logError()函數(shù)接收兩個(gè)參數(shù):表示嚴(yán)重程度的數(shù)值或字符串(視所用系統(tǒng)而異)及錯(cuò)誤消

息。其中,使用了 Image 對(duì)象來(lái)發(fā)送請(qǐng)求,這樣做非常靈活,主要表現(xiàn)如下幾方面。

? 所有瀏覽器都支持 Image 對(duì)象,包括那些不支持 XMLHttpRequest 對(duì)象的瀏覽器。

? 可以避免跨域限制。通常都是一臺(tái)服務(wù)器要負(fù)責(zé)處理多臺(tái)服務(wù)器的錯(cuò)誤,而這種情況下使用

XMLHttpRequest 是不行的。

? 在記錄錯(cuò)誤的過(guò)程中出問(wèn)題的概率比較低。大多數(shù) Ajax 通信都是由 JavaScript 庫(kù)提供的包裝函

數(shù)來(lái)處理的,如果庫(kù)代碼本身有問(wèn)題,而你還在依賴該庫(kù)記錄錯(cuò)誤,可想而知,錯(cuò)誤消息是不

可能得到記錄的。

只要是使用 try-catch 語(yǔ)句,就應(yīng)該把相應(yīng)錯(cuò)誤記錄到日志中。來(lái)看下面的例子。

for (var i=0, len=mods.length; i < len; i++){

try {

mods[i].init();

} catch (ex){

logError(\"nonfatal\", \"Module init failed: \" + ex.message);

}

}

在這里,一旦模塊初始化失敗,就會(huì)調(diào)用 logError()。第一個(gè)參數(shù)是\"nonfatal\"(非致命),表

示錯(cuò)誤的嚴(yán)重程度。第二個(gè)參數(shù)是上下文信息加上真正的 JavaScript 錯(cuò)誤消息。記錄到服務(wù)器中的錯(cuò)誤

消息應(yīng)該盡可能多地帶有上下文信息,以便鑒別導(dǎo)致錯(cuò)誤的真正原因。

17.3 調(diào)試技術(shù)

在不那么容易找到 JavaScript 調(diào)試程序的年代,開發(fā)人員不得不發(fā)揮自己的創(chuàng)造力,通過(guò)各種方法

來(lái)調(diào)試自己的代碼。結(jié)果,就出現(xiàn)了以這樣或那樣的方式置入代碼,從而輸出調(diào)試信息的做法。其中,

最常見的做法就是在要調(diào)試的代碼中隨處插入 alert()函數(shù)。但這種做法一方面比較麻煩(調(diào)試之后還

需要清理),另一方面還可能引入新問(wèn)題(想象一下把某個(gè) alert()函數(shù)遺留在產(chǎn)品代碼中的結(jié)果)。如

今,已經(jīng)有了很多更好的調(diào)試工具,因此我們也不再建議在調(diào)試中使用 alert()了。

17.3.1 將消息記錄到控制臺(tái)

IE8、Firefox、Opera、Chrome 和 Safari 都有 JavaScript 控制臺(tái),可以用來(lái)查看 JavaScript 錯(cuò)誤。而

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

第531頁(yè)

17.3 調(diào)試技術(shù) 513

14

2

3

17

5

13

6

7

8

9

10

11

12

且,在這些瀏覽器中,都可以通過(guò)代碼向控制臺(tái)輸出消息。對(duì) Firefox 而言,需要安裝 Firebug

(www.getfirebug.com),因?yàn)?Firefox 要使用 Firebug 的控制臺(tái)。對(duì) IE8、Firefox、Chrome 和 Safari 來(lái)說(shuō),

則可以通過(guò) console 對(duì)象向 JavaScript 控制臺(tái)中寫入消息,這個(gè)對(duì)象具有下列方法。

? error(message):將錯(cuò)誤消息記錄到控制臺(tái)

? info(message):將信息性消息記錄到控制臺(tái)

? log(message):將一般消息記錄到控制臺(tái)

? warn(message):將警告消息記錄到控制臺(tái)

在 IE8、Firebug、Chrome 和 Safari 中,用來(lái)記錄消息的方法不同,控制臺(tái)中顯示的錯(cuò)誤消息也不

一樣。錯(cuò)誤消息帶有紅色圖標(biāo),而警告消息帶有黃色圖標(biāo)。以下函數(shù)展示了使用控制臺(tái)輸出消息的一

個(gè)示例。

function sum(num1, num2){

console.log(\"Entering sum(), arguments are \" + num1 + \",\" + num2);

console.log(\"Before calculation\");

var result = num1 + num2;

console.log(\"After calculation\");

console.log(\"Exiting sum()\");

return result;

}

在調(diào)用這個(gè) sum()函數(shù)時(shí),控制臺(tái)中會(huì)出現(xiàn)一些消息,可以用來(lái)輔助調(diào)試。在 Safari 中,通過(guò)

“Develop”(開發(fā))菜單可以打開其 JavaScript 控制臺(tái)(前面討論過(guò));在 Chrome 中,單擊“Control this

page”(控制當(dāng)前頁(yè))按鈕并選擇“Developer”(開發(fā)人員)和“JavaScript console”(JavaScript 控制臺(tái))

即可;而在 Firefox 中,要打開控制臺(tái)需要單擊 Firefox 狀態(tài)欄右下角的圖標(biāo)。IE8 的控制臺(tái)是其 Developer

Tools(開發(fā)人員工具)擴(kuò)展的一部分,通過(guò)“Tools”(工具)菜單可以找到,其控制臺(tái)在“Script”(腳

本)選項(xiàng)卡中。

Opera 10.5 之前的版本中,JavaScript 控制臺(tái)可以通過(guò) opera.postError()方法來(lái)訪問(wèn)。這個(gè)方法

接受一個(gè)參數(shù),即要寫入到控制臺(tái)中的參數(shù),其用法如下。

function sum(num1, num2){

opera.postError(\"Entering sum(), arguments are \" + num1 + \",\" + num2);

opera.postError(\"Before calculation\");

var result = num1 + num2;

opera.postError(\"After calculation\");

opera.postError(\"Exiting sum()\");

return result;

}

別看 opera.postError()方法的名字好像是只能輸出錯(cuò)誤,但實(shí)際上能通過(guò)它向 JavaScript 控制

臺(tái)中寫入任何信息。

還有一種方案是使用 LiveConnect,也就是在 JavaScript 中運(yùn)行 Java 代碼。Firefox、Safari 和 Opera

都支持 LiveConnect,因此可以操作 Java 控制臺(tái)。例如,通過(guò)下列代碼就可以在 JavaScript 中把消息寫

入到 Java 控制臺(tái)。

java.lang.System.out.println(\"Your message\");

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

第532頁(yè)

514 第 17 章 錯(cuò)誤處理與調(diào)試

可以用這行代碼替代 console.log()或 opera.postError(),如下所示。

function sum(num1, num2){

java.lang.System.out.println(\"Entering sum(), arguments are \" + num1 + \",\" + num2);

java.lang.System.out.println(\"Before calculation\");

var result = num1 + num2;

java.lang.System.out.println(\"After calculation\");

java.lang.System.out.println(\"Exiting sum()\");

return result;

}

如果系統(tǒng)設(shè)置恰當(dāng),可以在調(diào)用 LiveConnect 時(shí)就立即顯示 Java 控制臺(tái)。在 Firefox 中,通過(guò)“Tools”

(工具)菜單可以打開 Java 控制臺(tái);在 Opera 中,要打開 Java 控制臺(tái),可以選擇菜單“Tools”(工具)

及“Advanced”(高級(jí))。Safari 沒有內(nèi)置對(duì) Java 控制臺(tái)的支持,必須單獨(dú)運(yùn)行。

不存在一種跨瀏覽器向 JavaScript 控制臺(tái)寫入消息的機(jī)制,但下面的函數(shù)倒可以作為統(tǒng)一的接口。

function log(message){

if (typeof console == \"object\"){

console.log(message);

} else if (typeof opera == \"object\"){

opera.postError(message);

} else if (typeof java == \"object\" && typeof java.lang == \"object\"){

java.lang.System.out.println(message);

}

}

ConsoleLoggingExample01.htm

這個(gè) log()函數(shù)檢測(cè)了哪個(gè) JavaScript 控制臺(tái)接口可用,然后使用相應(yīng)的接口。可以在任何瀏覽器

中安全地使用這個(gè)函數(shù),不會(huì)導(dǎo)致任何錯(cuò)誤,例如:

function sum(num1, num2){

log(\"Entering sum(), arguments are \" + num1 + \",\" + num2);

log(\"Before calculation\");

var result = num1 + num2;

log(\"After calculation\");

log(\"Exiting sum()\");

return result;

}

ConsoleLoggingExample01.htm

向 JavaScript 控制臺(tái)中寫入消息可以輔助調(diào)試代碼,但在發(fā)布應(yīng)用程序時(shí),還必須要移除所有消息。

在部署應(yīng)用程序時(shí),可以通過(guò)手工或通過(guò)特定的代碼處理步驟來(lái)自動(dòng)完成清理工作。

記錄消息要比使用 alert()函數(shù)更可取,因?yàn)榫婵驎?huì)阻斷程序的執(zhí)行,而在測(cè)

定異步處理對(duì)時(shí)間的影響時(shí),使用警告框會(huì)影響結(jié)果。

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

第533頁(yè)

17.3 調(diào)試技術(shù) 515

14

2

3

17

5

13

6

7

8

9

10

11

12

17.3.2 將消息記錄到當(dāng)前頁(yè)面

另一種輸出調(diào)試消息的方式,就是在頁(yè)面中開辟一小塊區(qū)域,用以顯示消息。這個(gè)區(qū)域通常是一個(gè)

元素,而該元素可以總是出現(xiàn)在頁(yè)面中,但僅用于調(diào)試目的;也可以是一個(gè)根據(jù)需要?jiǎng)討B(tài)創(chuàng)建的元素。

例如,可以將 log()函數(shù)修改為如下所示:

function log(message){

var console = document.getElementById(\"debuginfo\");

if (console === null){

console = document.createElement(\"div\");

console.id = \"debuginfo\";

console.style.background = \"#dedede\";

console.style.border = \"1px solid silver\";

console.style.padding = \"5px\";

console.style.width = \"400px\";

console.style.position = \"absolute\";

console.style.right = \"0px\";

console.style.top = \"0px\";

document.body.appendChild(console);

}

console.innerHTML += \"<p>\" + message + \"</p>\";

}

PageLoggingExample01.htm

這個(gè)修改后的 log()函數(shù)首先檢測(cè)是否已經(jīng)存在調(diào)試元素,如果沒有則會(huì)新創(chuàng)建一個(gè)<div>元素,

并為該元素應(yīng)用一些樣式,以便與頁(yè)面中的其他元素區(qū)別開。然后,又使用 innerHTML 將消息寫入到

這個(gè)<div>元素中。結(jié)果就是頁(yè)面中會(huì)有一小塊區(qū)域顯示錯(cuò)誤消息。這種技術(shù)在不支持 JavaScript 控制

臺(tái)的 IE7 及更早版本或其他瀏覽器中十分有用。

與把錯(cuò)誤消息記錄到控制臺(tái)相似,把錯(cuò)誤消息輸出到頁(yè)面的代碼也要在發(fā)布前

刪除。

17.3.3 拋出錯(cuò)誤

如前所述,拋出錯(cuò)誤也是一種調(diào)試代碼的好辦法。如果錯(cuò)誤消息很具體,基本上就可以把它當(dāng)作確

定錯(cuò)誤來(lái)源的依據(jù)。但這種錯(cuò)誤消息必須能夠明確給出導(dǎo)致錯(cuò)誤的原因,才能省去其他調(diào)試操作。來(lái)看

下面的函數(shù):

function divide(num1, num2){

return num1 / num2;

}

這個(gè)簡(jiǎn)單的函數(shù)計(jì)算兩個(gè)數(shù)的除法,但如果有一個(gè)參數(shù)不是數(shù)值,它會(huì)返回 NaN。類似這樣簡(jiǎn)單的

計(jì)算如果返回 NaN,就會(huì)在 Web 應(yīng)用程序中導(dǎo)致問(wèn)題。對(duì)此,可以在計(jì)算之前,先檢測(cè)每個(gè)參數(shù)是否都

是數(shù)值。例如:

function divide(num1, num2){

if (typeof num1 != \"number\" || typeof num2 != \"number\"){

throw new Error(\"divide(): Both arguments must be numbers.\");

}

return num1 / num2;

}

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

第534頁(yè)

516 第 17 章 錯(cuò)誤處理與調(diào)試

在此,如果有一個(gè)參數(shù)不是數(shù)值,就會(huì)拋出錯(cuò)誤。錯(cuò)誤消息中包含了函數(shù)的名字,以及導(dǎo)致錯(cuò)誤的

真正原因。瀏覽器只要報(bào)告了這個(gè)錯(cuò)誤消息,我們就可以立即知道錯(cuò)誤來(lái)源及問(wèn)題的性質(zhì)。相對(duì)來(lái)說(shuō),

這種具體的錯(cuò)誤消息要比那些泛泛的瀏覽器錯(cuò)誤消息更有用。

對(duì)于大型應(yīng)用程序來(lái)說(shuō),自定義的錯(cuò)誤通常都使用 assert()函數(shù)拋出。這個(gè)函數(shù)接受兩個(gè)參數(shù),

一個(gè)是求值結(jié)果應(yīng)該為 true 的條件,另一個(gè)是條件為 false 時(shí)要拋出的錯(cuò)誤。以下就是一個(gè)非?;?/p>

的 assert()函數(shù)。

function assert(condition, message){

if (!condition){

throw new Error(message);

}

}

AssertExample01.htm

可以用這個(gè) assert()函數(shù)代替某些函數(shù)中需要調(diào)試的 if 語(yǔ)句,以便輸出錯(cuò)誤消息。下面是使用

這個(gè)函數(shù)的例子。

function divide(num1, num2){

assert(typeof num1 == \"number\" && typeof num2 == \"number\",

\"divide(): Both arguments must be numbers.\");

return num1 / num2;

}

AssertExample01.htm

可見,使用 assert()函數(shù)可以減少拋出錯(cuò)誤所需的代碼量,而且也比前面的代碼更容易看懂。

17.4 常見的 IE 錯(cuò)誤

多年以來(lái),IE 一直都是最難于調(diào)試 JavaScript 錯(cuò)誤的瀏覽器。IE 給出的錯(cuò)誤消息一般很短又語(yǔ)焉不

詳,而且上下文信息也很少,有時(shí)甚至一點(diǎn)都沒有。但作為用戶最多的瀏覽器,如何看懂 IE 給出的錯(cuò)

誤也是最受關(guān)注的。下面幾小節(jié)將分別探討一些在 IE 中難于調(diào)試的 JavaScript 錯(cuò)誤。

17.4.1 操作終止

在 IE8 之前的版本中,存在一個(gè)相對(duì)于其他瀏覽器而言,最令人迷惑、討厭,也最難于調(diào)試的錯(cuò)誤:

操作終止(operation aborted)。在修改尚未加載完成的頁(yè)面時(shí),就會(huì)發(fā)生操作終止錯(cuò)誤。發(fā)生錯(cuò)誤時(shí),

會(huì)出現(xiàn)一個(gè)模態(tài)對(duì)話框,告訴你“操作終止?!眴螕舸_定(OK)按鈕,則卸載整個(gè)頁(yè)面,繼而顯示一張

空白屏幕;此時(shí)要進(jìn)行調(diào)試非常困難。下面的示例將會(huì)導(dǎo)致操作終止錯(cuò)誤。

<!DOCTYPE html>

<html>

<head>

<title>Operation Aborted Example</title>

</head>

<body>

<p>The following code should cause an Operation Aborted error in IE versions

prior to 8.</p>

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

第535頁(yè)

17.4 常見的 IE 錯(cuò)誤 517

14

2

3

17

5

13

6

7

8

9

10

11

12

<div>

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

document.body.appendChild(document.createElement(\"div\"));

</script>

</div>

</body>

</html>

OperationAbortedExample01.htm

這個(gè)例子中存在的問(wèn)題是:JavaScript 代碼在頁(yè)面尚未加載完畢時(shí)就要修改 document.body,而且

<script>元素還不是<body>元素的直接子元素。準(zhǔn)確一點(diǎn)說(shuō),當(dāng)<script>節(jié)點(diǎn)被包含在某個(gè)元素中,

而且 JavaScript 代碼又要使用 appendChild()、innerHTML 或其他 DOM 方法修改該元素的父元素或

祖先元素時(shí),將會(huì)發(fā)生操作終止錯(cuò)誤(因?yàn)橹荒苄薷囊呀?jīng)加載完畢的元素)。

要避免這個(gè)問(wèn)題,可以等到目標(biāo)元素加載完畢后再對(duì)它進(jìn)行操作,或者使用其他操作方法。例如,

為 document.body 添加一個(gè)絕對(duì)定位在頁(yè)面上的覆蓋層,就是一種非常常見的操作。通常,開發(fā)人員

都是使用 appendChild()方法來(lái)添加這個(gè)元素的,但換成使用 insertBefore()方法也很容易。因此,

只要修改前面例子中的一行代碼,就可以避免操作終止錯(cuò)誤。

<!DOCTYPE html>

<html>

<head>

<title>Operation Aborted Example</title>

</head>

<body>

<p>The following code should not cause an Operation Aborted error in IE

versions prior to 8.</p>

<div>

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

document.body.insertBefore(document.createElement(\"div\"),

document.body.firstChild);

</script>

</div>

</body>

</html>

OperationAbortedExample02.htm

在這個(gè)例子中,新的<div>元素被添加到 document.body 的開頭部分而不是末尾。因?yàn)橥瓿蛇@一

操作所需的所有信息在腳本運(yùn)行時(shí)都是已知的,所以這不會(huì)引發(fā)錯(cuò)誤。

除了改變方法之外,還可以把<script>元素從包含元素中移出來(lái),直接作為<body>的子元素。例如:

<!DOCTYPE html>

<html>

<head>

<title>Operation Aborted Example</title>

</head>

<body>

<p>The following code should not cause an Operation Aborted error in IE

versions prior to 8.</p>

<div>

</div>

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

document.body.appendChild(document.createElement(\"div\"));

</script>

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

第536頁(yè)

518 第 17 章 錯(cuò)誤處理與調(diào)試

</body>

</html>

OperationAbortedExample03.htm

這一次也不會(huì)發(fā)生錯(cuò)誤,因?yàn)槟_本修改的是它的直接父元素,而不再是間接的祖先元素。

在同樣的情況下,IE8 不再拋出操作終止錯(cuò)誤,而是拋出常規(guī)的 JavaScript 錯(cuò)誤,帶有如下錯(cuò)誤消息:

HTML Parsing Error: Unable to modify the parent container element before the child

element is closed (KB927917).

不過(guò),雖然瀏覽器拋出的錯(cuò)誤不同,但解決方案仍然是一樣的。

17.4.2 無(wú)效字符

根據(jù)語(yǔ)法,JavaScript 文件必須只包含特定的字符。在 JavaScript 文件中存在無(wú)效字符時(shí),IE 會(huì)拋出

無(wú)效字符(invalid character)錯(cuò)誤。所謂無(wú)效字符,就是 JavaScript 語(yǔ)法中未定義的字符。例如,有一

個(gè)很像減號(hào)但卻由 Unicode 值 8211 表示的字符(\–),就不能用作常規(guī)的減號(hào)(ASCII 編碼為 45),

因?yàn)?JavaScript 語(yǔ)法中沒有定義該字符。這個(gè)字符通常是在 Word 文檔中自動(dòng)插入的。如果你的代碼是

從 Word 文檔中復(fù)制到文本編輯器中,然后又在 IE 中運(yùn)行的,那么就可能會(huì)遇到無(wú)效字符錯(cuò)誤。其他瀏

覽器對(duì)無(wú)效字符做出的反應(yīng)與 IE 類似,F(xiàn)irefox 會(huì)拋出非法字符(illegal character)錯(cuò)誤,Safari 會(huì)報(bào)告

發(fā)生了語(yǔ)法錯(cuò)誤,而 Opera 則會(huì)報(bào)告發(fā)生了 ReferenceError(引用錯(cuò)誤),因?yàn)樗鼤?huì)將無(wú)效字符解釋

為未定義的標(biāo)識(shí)符。

17.4.3 未找到成員

如前所述,IE 中的所有 DOM 對(duì)象都是以 COM 對(duì)象,而非原生 JavaScript 對(duì)象的形式實(shí)現(xiàn)的。這

會(huì)導(dǎo)致一些與垃圾收集相關(guān)的非常奇怪的行為。IE 中的未找到成員(Member not found)錯(cuò)誤,就是由

于垃圾收集例程配合錯(cuò)誤所直接導(dǎo)致的。

具體來(lái)說(shuō),如果在對(duì)象被銷毀之后,又給該對(duì)象賦值,就會(huì)導(dǎo)致未找到成員錯(cuò)誤。而導(dǎo)致這個(gè)錯(cuò)誤

的,一定是 COM 對(duì)象。發(fā)生這個(gè)錯(cuò)誤的最常見情形是使用 event 對(duì)象的時(shí)候。IE 中的 event 對(duì)象是

window 的屬性,該對(duì)象在事件發(fā)生時(shí)創(chuàng)建,在最后一個(gè)事件處理程序執(zhí)行完畢后銷毀。假設(shè)你在一個(gè)

閉包中使用了 event 對(duì)象,而該閉包不會(huì)立即執(zhí)行,那么在將來(lái)調(diào)用它并給 event 的屬性賦值時(shí),就

會(huì)導(dǎo)致未找到成員錯(cuò)誤,如下面的例子所示。

document.onclick = function(){

var event = window.event;

setTimeout(function(){

event.returnValue = false; //未找到成員錯(cuò)誤

}, 1000);

};

在這段代碼中,我們將一個(gè)單擊事件處理程序指定給了文檔。在事件處理程序中,window.event

被保存在 event 變量中。然后,傳入 setTimeout()中的閉包里又包含了 event 變量。當(dāng)單擊事件處

理程序執(zhí)行完畢后,event 對(duì)象就會(huì)被銷毀,因而閉包中引用對(duì)象的成員就成了不存在的了。換句話說(shuō),

由于不能在 COM 對(duì)象被銷毀之后再給其成員賦值,在閉包中給 returnValue 賦值就會(huì)導(dǎo)致未找到成

員錯(cuò)誤。

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

第537頁(yè)

17.4 常見的 IE 錯(cuò)誤 519

14

2

3

17

5

13

6

7

8

9

10

11

12

17.4.4 未知運(yùn)行時(shí)錯(cuò)誤

當(dāng)使用 innerHTML 或 outerHTML 以下列方式指定 HTML 時(shí),就會(huì)發(fā)生未知運(yùn)行時(shí)錯(cuò)誤(Unknown

runtime error):一是把塊元素插入到行內(nèi)元素時(shí),二是訪問(wèn)表格任意部分(<table>、<tbody>等)的

任意屬性時(shí)。例如,從技術(shù)角度說(shuō),<span>標(biāo)簽不能包含<div>之類的塊級(jí)元素,因此下面的代碼就會(huì)

導(dǎo)致未知運(yùn)行時(shí)錯(cuò)誤:

span.innerHTML = \"<div>Hi</div>\"; //這里,span 包含了<div>元素

在遇到把塊級(jí)元素插入到不恰當(dāng)位置的情況時(shí),其他瀏覽器會(huì)嘗試糾正并隱藏錯(cuò)誤,而 IE 在這一

點(diǎn)上反倒很較真兒。

17.4.5 語(yǔ)法錯(cuò)誤

通常,只要 IE 一報(bào)告發(fā)生了語(yǔ)法錯(cuò)誤(syntax error),都可以很快找到錯(cuò)誤的原因。這時(shí)候,原因

可能是代碼中少了一個(gè)分號(hào),或者花括號(hào)前后不對(duì)應(yīng)。然而,還有一種原因不十分明顯的情況需要格外

注意。

如果你引用了外部的 JavaScript 文件,而該文件最終并沒有返回 JavaScript 代碼,IE 也會(huì)拋出語(yǔ)法

錯(cuò)誤。例如,<script>元素的 src 特性指向了一個(gè) HTML 文件,就會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤。報(bào)告語(yǔ)法錯(cuò)誤的

位置時(shí),通常都會(huì)說(shuō)該錯(cuò)誤位于腳本第一行的第一個(gè)字符處。Opera 和 Safari 也會(huì)報(bào)告語(yǔ)法錯(cuò)誤,但它

們會(huì)給出導(dǎo)致問(wèn)題的外部文件的信息;IE 就不會(huì)給出這個(gè)信息,因此就需要我們自己重復(fù)檢查一遍引用

的外部 JavaScript 文件。但 Firefox 會(huì)忽略那些被當(dāng)作 JavaScript 內(nèi)容嵌入到文檔中的非 JavaScript 文件中的

解析錯(cuò)誤。

在服務(wù)器端組件動(dòng)態(tài)生成 JavaScript 的情況下,比較容易出現(xiàn)這種錯(cuò)誤。很多服務(wù)器端語(yǔ)言都會(huì)在

發(fā)生運(yùn)行時(shí)錯(cuò)誤時(shí),向輸出中插入 HTML 代碼,而這種包含 HTML 的輸出很容易就會(huì)違反 JavaScript

語(yǔ)法。如果在追查語(yǔ)法錯(cuò)誤時(shí)遇到了麻煩,我們建議你再仔細(xì)檢查一遍引用的外部文件,確保這些文件

中沒有包含服務(wù)器因錯(cuò)誤而插入到其中的 HTML。

17.4.6 系統(tǒng)無(wú)法找到指定資源

系統(tǒng)無(wú)法找到指定資源(The system cannot locate the resource specified)這種說(shuō)法,恐怕要算是 IE

給出的最有價(jià)值的錯(cuò)誤消息了。在使用 JavaScript 請(qǐng)求某個(gè)資源 URL,而該 URL 的長(zhǎng)度超過(guò)了 IE 對(duì) URL

最長(zhǎng)不能超過(guò) 2083 個(gè)字符的限制時(shí),就會(huì)發(fā)生這個(gè)錯(cuò)誤。IE 不僅限制 JavaScript 中使用的 URL 的長(zhǎng)度,

而且也限制用戶在瀏覽器自身中使用的 URL 長(zhǎng)度(其他瀏覽器對(duì) URL 的限制沒有這么嚴(yán)格)。IE 對(duì) URL

路徑還有一個(gè)不能超過(guò) 2048 個(gè)字符的限制。下面的代碼將會(huì)導(dǎo)致錯(cuò)誤。

function createLongUrl(url){

var s = \"?\";

for (var i=0, len=2500; i < len; i++){

s += \"a\";

}

return url + s;

}

var x = new XMLHttpRequest();

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

第538頁(yè)

520 第 17 章 錯(cuò)誤處理與調(diào)試

x.open(\"get\", createLongUrl(\"http://www.somedomain.com/\"), true);

x.send(null);

LongURLErrorExample01.htm

在這個(gè)例子中,XMLHttpRequest 對(duì)象試圖向一個(gè)超出最大長(zhǎng)度限制的 URL 發(fā)送請(qǐng)求。在調(diào)用

open()方法時(shí),就會(huì)發(fā)生錯(cuò)誤。避免這個(gè)問(wèn)題的辦法,無(wú)非就是通過(guò)給查詢字符串參數(shù)起更短的名字,

或者減少不必要的數(shù)據(jù),來(lái)縮短查詢字符串的長(zhǎng)度。另外,還可以把請(qǐng)求方法改為 POST,通過(guò)請(qǐng)求體

而不是查詢字符串來(lái)發(fā)送數(shù)據(jù)。有關(guān) Ajax(或者說(shuō) XMLHttpRequest 對(duì)象)的詳細(xì)內(nèi)容,將在第 21

章全面討論。

17.5 小結(jié)

錯(cuò)誤處理對(duì)于今天復(fù)雜的 Web 應(yīng)用程序開發(fā)而言至關(guān)重要。不能提前預(yù)測(cè)到可能發(fā)生的錯(cuò)誤,不

能提前采取恢復(fù)策略,可能導(dǎo)致較差的用戶體驗(yàn),最終引發(fā)用戶不滿。多數(shù)瀏覽器在默認(rèn)情況下都不會(huì)

向用戶報(bào)告錯(cuò)誤,因此在開發(fā)和調(diào)試期間需要啟用瀏覽器的錯(cuò)誤報(bào)告功能。然而,在投入運(yùn)行的產(chǎn)品代

碼中,則不應(yīng)該再有諸如此類的錯(cuò)誤報(bào)告出現(xiàn)。

下面是幾種避免瀏覽器響應(yīng) JavaScript 錯(cuò)誤的方法。

? 在可能發(fā)生錯(cuò)誤的地方使用 try-catch 語(yǔ)句,這樣你還有機(jī)會(huì)以適當(dāng)?shù)姆绞綄?duì)錯(cuò)誤給出響應(yīng),

而不必沿用瀏覽器處理錯(cuò)誤的機(jī)制。

? 使用 window.onerror 事件處理程序,這種方式可以接受 try-catch 不能處理的所有錯(cuò)誤(僅

限于 IE、Firefox 和 Chrome)。

另外,對(duì)任何 Web 應(yīng)用程序都應(yīng)該分析可能的錯(cuò)誤來(lái)源,并制定處理錯(cuò)誤的方案。

? 首先,必須要明確什么是致命錯(cuò)誤,什么是非致命錯(cuò)誤。

? 其次,再分析代碼,以判斷最可能發(fā)生的錯(cuò)誤。JavaScript 中發(fā)生錯(cuò)誤的主要原因如下。

? 類型轉(zhuǎn)換

? 未充分檢測(cè)數(shù)據(jù)類型

? 發(fā)送給服務(wù)器或從服務(wù)器接收到的數(shù)據(jù)有錯(cuò)誤

IE、Firefox、Chrome、Opera 和 Safari 都有 JavaScript 調(diào)試器,有的是內(nèi)置的,有的是以需要下載的

擴(kuò)展形式存在的。這些調(diào)試器都支持設(shè)置斷點(diǎn)、控制代碼執(zhí)行及在運(yùn)行時(shí)檢測(cè)變量的值。

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

第539頁(yè)

18.1 瀏覽器對(duì) XML DOM 的支持 521

14

2

3

17

18

13

6

7

8

9

10

11

12

JavaScript 與 XML

本章內(nèi)容

? 檢測(cè)瀏覽器對(duì) XML DOM 的支持

? 理解 JavaScript 中的 XPath

? 使用 XSLT 處理器

幾何時(shí),XML 一度成為存儲(chǔ)和通過(guò)因特網(wǎng)傳輸結(jié)構(gòu)化數(shù)據(jù)的標(biāo)準(zhǔn)。透過(guò) XML 的發(fā)展,能夠

清晰地看到 Web 技術(shù)發(fā)展的軌跡。DOM 規(guī)范的制定,不僅是為了方便在 Web 瀏覽器中使用

XML,也是為了在桌面及服務(wù)器應(yīng)用程序中處理 XML 數(shù)據(jù)。此前,由于瀏覽器無(wú)法解析 XML 數(shù)據(jù),

很多開發(fā)人員都要?jiǎng)邮志帉懽约旱?XML 解析器。而自從 DOM 出現(xiàn)后,所有瀏覽器都內(nèi)置了對(duì) XML 的

原生支持(XML DOM),同時(shí)也提供了一系列相關(guān)的技術(shù)支持。

18.1 瀏覽器對(duì) XML DOM 的支持

在正式的規(guī)范誕生以前,瀏覽器提供商實(shí)現(xiàn)的 XML 解決方案不僅對(duì) XML 的支持程度參差不齊,

而且對(duì)同一特性的支持也各不相同。DOM2 級(jí)是第一個(gè)提到動(dòng)態(tài)創(chuàng)建 XML DOM 概念的規(guī)范。DOM3

級(jí)進(jìn)一步增強(qiáng)了 XML DOM,新增了解析和序列化等特性。然而,當(dāng) DOM3 級(jí)規(guī)范的各項(xiàng)條款塵埃落

定之后,大多數(shù)瀏覽器也都實(shí)現(xiàn)了各自不同的解決方案。

18.1.1 DOM2 級(jí)核心

我們?cè)诘?2章曾經(jīng)提到過(guò),DOM2級(jí)在document.implementation 中引入了createDocument()

方法。IE9+、Firefox、Opera、Chrome 和 Safari 都支持這個(gè)方法。想一想,或許你還記得可以在支持 DOM2

級(jí)的瀏覽器中使用以下語(yǔ)法來(lái)創(chuàng)建一個(gè)空白的 XML 文檔:

var xmldom = document.implementation.createDocument(namespaceUri, root, doctype);

在通過(guò) JavaScript 處理 XML 時(shí),通常只使用參數(shù) root,因?yàn)檫@個(gè)參數(shù)指定的是 XML DOM 文檔元

素的標(biāo)簽名。而 namespaceUri 參數(shù)則很少用到,原因是在 JavaScrip 中管理命名空間比較困難。最后,

doctype 參數(shù)用得就更少了。

因此,要想創(chuàng)建一個(gè)新的、文檔元素為<root>的 XML 文檔,可以使用如下代碼:

var xmldom = document.implementation.createDocument(\"\", \"root\", null);

alert(xmldom.documentElement.tagName); //\"root\"

var child = xmldom.createElement(\"child\");

xmldom.documentElement.appendChild(child);

DOMLevel2CoreExample01.htm

第 18 章

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

第540頁(yè)

522 第 18 章 JavaScript 與 XML

這個(gè)例子創(chuàng)建了一個(gè) XML DOM 文檔,沒有默認(rèn)的命名空間,也沒有文檔類型。但要注意的是,盡

管不需要指定命名空間和文檔類型,也必須傳入相應(yīng)的參數(shù)。具體來(lái)說(shuō),給命名空間 URI 傳入一個(gè)空字

符串,就意味著未指定命名空間,而給文檔類型傳入 null,就意味著不指定文檔類型。變量 xmldom

中保存著一個(gè) DOM2 級(jí) Document 類型的實(shí)例,帶有第 12 章討論過(guò)的所有 DOM 方法和屬性。我們這

個(gè)例子顯示了文檔元素的標(biāo)簽名,然后又創(chuàng)建并給文檔元素添加了一個(gè)新的子元素。

要檢測(cè)瀏覽器是否支持 DOM2 級(jí) XML,可以使用下面這行代碼:

var hasXmlDom = document.implementation.hasFeature(\"XML\", \"2.0\");

在實(shí)際開發(fā)中,很少需要從頭開始創(chuàng)建一個(gè) XML 文檔,然后再使用 DOM 文檔為其添加元素。更

常見的情況往往是將某個(gè) XML 文檔解析為 DOM 結(jié)構(gòu),或者反之。由于 DOM2 級(jí)規(guī)范沒有提供這種功

能,因此就出現(xiàn)了一些事實(shí)標(biāo)準(zhǔn)。

18.1.2 DOMParser類型

為了將 XML 解析為 DOM 文檔,F(xiàn)irefox 引入了 DOMParser 類型;后來(lái),IE9、Safari、Chrome 和

Opera 也支持了這個(gè)類型。在解析 XML 之前,首先必須創(chuàng)建一個(gè) DOMParser 的實(shí)例,然后再調(diào)用

parseFromString()方法。這個(gè)方法接受兩個(gè)參數(shù):要解析的 XML 字符串和內(nèi)容類型(內(nèi)容類型始

終都應(yīng)該是\"text/xml\")。返回的值是一個(gè) Document 的實(shí)例。來(lái)看下面的例子。

var parser = new DOMParser();

var xmldom = parser.parseFromString(\"<root><child/></root>\", \"text/xml\");

alert(xmldom.documentElement.tagName); //\"root\"

alert(xmldom.documentElement.firstChild.tagName); //\"child\"

var anotherChild = xmldom.createElement(\"child\");

xmldom.documentElement.appendChild(anotherChild);

var children = xmldom.getElementsByTagName(\"child\");

alert(children.length); //2

DOMParserExample01.htm

在這個(gè)例子中,我們把一個(gè)簡(jiǎn)單的 XML 字符串解析成了一個(gè) DOM 文檔。解析得到的 DOM 結(jié)構(gòu)以

<root>作為其文檔元素,該元素還有一個(gè)<child>子元素。此后,就可以使用 DOM 方法對(duì)返回的這個(gè)

文檔進(jìn)行操作了。

DOMParser 只能解析格式良好的 XML,因而不能把 HTML 解析為 HTML 文檔。在發(fā)生解析錯(cuò)誤

時(shí),仍然會(huì)從 parseFromString()中返回一個(gè) Document 對(duì)象,但這個(gè)對(duì)象的文檔元素是

<parsererror>,而文檔元素的內(nèi)容是對(duì)解析錯(cuò)誤的描述。下面是一個(gè)例子。

<parsererror xmlns=\"http://www.mozilla.org/newlayout/xml/parsererror.xml\">XML

Parsing Error: no element found Location: file:///I:/My%20Writing/My%20Books/

Professional%20JavaScript/Second%20Edition/Examples/Ch15/DOMParserExample2.htm Line

Number 1, Column 7: <sourcetext> & lt;root & gt; ------^</sourcetext > < /parsererror>

Firefox和 Opera都會(huì)返回這種格式的文檔。Safari和 Chrome 返回的文檔也包含<parsererror>元素,

但該元素會(huì)出現(xiàn)在發(fā)生解析錯(cuò)誤的地方。IE9 會(huì)在調(diào)用 parseFromString()的地方拋出一個(gè)解析錯(cuò)誤。

由于存在這些差別,因此確定是否發(fā)生解析錯(cuò)誤的最佳方式就是,使用一個(gè) try-catch 語(yǔ)句塊,如果沒

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

第541頁(yè)

18.1 瀏覽器對(duì) XML DOM 的支持 523

14

2

3

17

18

13

6

7

8

9

10

11

12

有錯(cuò)誤,則通過(guò) getElementsByTagName()來(lái)查找文檔中是否存在<parsererror>元素,如下面的例

子所示。

var parser = new DOMParser(),

xmldom,

errors;

try {

xmldom = parser.parseFromString(\"<root>\", \"text/xml\");

errors = xmldom.getElementsByTagName(\"parsererror\");

if (errors.length > 0){

throw new Error(\"Parsing error!\");

}

} catch (ex) {

alert(\"Parsing error!\");

}

DOMParserExample02.htm

這例子顯示,要解析的字符串中缺少了閉標(biāo)簽</root>,而這會(huì)導(dǎo)致解析錯(cuò)誤。在 IE9+中,此時(shí)會(huì)

拋出錯(cuò)誤。在 Firefox 和 Opera 中,文檔元素將是<parsererror>,而在 Safari 和 Chrome 中,

<parsererror>是<root>的第一個(gè)子元素。調(diào)用 getElementsByTagName(\"parsererror\")能夠

應(yīng)對(duì)這兩種情況。如果這個(gè)方法返回了元素,就說(shuō)明有錯(cuò)誤發(fā)生,繼而通過(guò)一個(gè)警告框顯示出來(lái)。當(dāng)

然,你還可以更進(jìn)一步,從錯(cuò)誤元素中提取出錯(cuò)誤信息。

18.1.3 XMLSerializer類型

在引入 DOMParser 的同時(shí),F(xiàn)irefox 還引入了 XMLSerializer 類型,提供了相反的功能:將 DOM

文檔序列化為 XML 字符串。后來(lái),IE9+、Opera、Chrome 和 Safari 都支持了 XMLSerializer。

要序列化 DOM 文檔,首先必須創(chuàng)建 XMLSerializer 的實(shí)例,然后將文檔傳入其 serializeToString ()方法,如下面的例子所示。

var serializer = new XMLSerializer();

var xml = serializer.serializeToString(xmldom);

alert(xml);

XMLSerializerExample01.htm

但是,serializeToString()方法返回的字符串并不適合打印,因此看起來(lái)會(huì)顯得亂糟糟的。

XMLSerializer 可以序列化任何有效的 DOM 對(duì)象,不僅包括個(gè)別的節(jié)點(diǎn),也包括 HTML 文檔。

將 HTML 文檔傳入 serializeToString()以后,HTML 文檔將被視為 XML 文檔,因此得到的代碼也

將是格式良好的。

如果將非 DOM 對(duì)象傳入 serializeToString(),會(huì)導(dǎo)致錯(cuò)誤發(fā)生。

18.1.4 IE8 及之前版本中的XML

事實(shí)上,IE 是第一個(gè)原生支持 XML 的瀏覽器,而這一支持是通過(guò) ActiveX 對(duì)象實(shí)現(xiàn)的。為了便于

桌面應(yīng)用程序開發(fā)人員處理 XML,微軟創(chuàng)建了 MSXML 庫(kù);但微軟并沒有針對(duì) JavaScript 創(chuàng)建不同的對(duì)

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

第542頁(yè)

524 第 18 章 JavaScript 與 XML

象,而只是讓 Web 開發(fā)人員能夠通過(guò)瀏覽器訪問(wèn)相同的對(duì)象。

第 8 章曾經(jīng)介紹過(guò) ActiveXObject 類型,通過(guò)這個(gè)類型可以在 JavaScript 中創(chuàng)建 ActiveX 對(duì)象的

實(shí)例。同樣,要?jiǎng)?chuàng)建一個(gè) XML 文檔的實(shí)例,也要使用 ActiveXObject 構(gòu)造函數(shù)并為其傳入一個(gè)表示

XML 文檔版本的字符串。有 6 種不同的 XML 文檔版本可以供選擇。

? Microsoft.XmlDom:最初隨同 IE 發(fā)布;不建議使用。

? MSXML2.DOMDocument:為方便腳本處理而更新的版本,建議僅在特殊情況下作為后備版本

使用。

? MSXML2.DOMDocument.3.0:為了在 JavaScript 中使用,這是最低的建議版本。

? MSXML2.DOMDocument.4.0:在通過(guò)腳本處理時(shí)并不可靠,使用這個(gè)版本可能導(dǎo)致安全警告。

? MSXML2.DOMDocument.5.0:在通過(guò)腳本處理時(shí)并不可靠,使用這個(gè)版本同樣可能導(dǎo)致安全

警告。

? MSXML2.DOMDocument.6.0:通過(guò)腳本能夠可靠處理的最新版本。

在這 6 個(gè)版本中,微軟只推薦使用 MSXML2.DOMDocument.6.0 或 MSXML2.DOMDocument.3.0;

前者是最新最可靠的版本,而后者則是大多數(shù) Windows 操作系統(tǒng)都支持的版本??梢宰鳛楹髠浒姹镜?/p>

MSXML2.DOMDocument,僅在針對(duì) IE5.5 之前的瀏覽器開發(fā)時(shí)才有必要使用。

通過(guò)嘗試創(chuàng)建每個(gè)版本的實(shí)例并觀察是否有錯(cuò)誤發(fā)生,可以確定哪個(gè)版本可用。例如:

function createDocument(){

if (typeof arguments.callee.activeXString != \"string\"){

var versions = [\"MSXML2.DOMDocument.6.0\", \"MSXML2.DOMDocument.3.0\",

\"MSXML2.DOMDocument\"],

i, len;

for (i=0,len=versions.length; i < len; i++){

try {

new ActiveXObject(versions[i]);

arguments.callee.activeXString = versions[i];

break;

} catch (ex){

//跳過(guò)

}

}

}

return new ActiveXObject(arguments.callee.activeXString);

}

IEXmlDomExample01.htm

這個(gè)函數(shù)中使用 for 循環(huán)迭代了每個(gè)可能的 ActiveX 版本。如果版本無(wú)效,則創(chuàng)建新

ActiveXObject 的調(diào)用就會(huì)拋出錯(cuò)誤;此時(shí),catch 語(yǔ)句會(huì)捕獲錯(cuò)誤,循環(huán)繼續(xù)。如果沒有發(fā)生錯(cuò)誤,

則可用的版本將被保存在這個(gè)函數(shù)的 activeXString 屬性中。這樣,就不必在每次調(diào)用這個(gè)函數(shù)時(shí)都

重復(fù)檢查可用版本了——直接創(chuàng)建并返回對(duì)象即可。

要解析 XML 字符串,首先必須創(chuàng)建一個(gè) DOM 文檔,然后調(diào)用 loadXML()方法。新創(chuàng)建的 XML

文檔完全是一個(gè)空文檔,因而不能對(duì)其執(zhí)行任何操作。為 loadXML()方法傳入的 XML 字符串經(jīng)解析之

后會(huì)被填充到 DOM 文檔中。來(lái)看下面的例子。

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

第543頁(yè)

18.1 瀏覽器對(duì) XML DOM 的支持 525

14

2

3

17

18

13

6

7

8

9

10

11

12

var xmldom = createDocument();

xmldom.loadXML(\"<root><child/></root>\");

alert(xmldom.documentElement.tagName); //\"root\"

alert(xmldom.documentElement.firstChild.tagName); //\"child\"

var anotherChild = xmldom.createElement(\"child\");

xmldom.documentElement.appendChild(anotherChild);

var children = xmldom.getElementsByTagName(\"child\");

alert(children.length); //2

IEXmlDomExample01.htm

在新 DOM 文檔中填充了 XML 內(nèi)容之后,就可以像操作其他 DOM 文檔一樣操作它了(可以使用任

何方法和屬性)。

如果解析過(guò)程中出錯(cuò),可以在 parseError 屬性中找到錯(cuò)誤消息。這個(gè)屬性本身是一個(gè)包含多個(gè)屬

性的對(duì)象,每個(gè)屬性都保存著有關(guān)解析錯(cuò)誤的某一方面信息。

? errorCode:錯(cuò)誤類型的數(shù)值編碼;在沒有發(fā)生錯(cuò)誤時(shí)值為 0。

? filePos:文件中導(dǎo)致錯(cuò)誤發(fā)生的位置。

? line:發(fā)生錯(cuò)誤的行。

? linepos:發(fā)生錯(cuò)誤的行中的字符。

? reason:對(duì)錯(cuò)誤的文本解釋。

? srcText:導(dǎo)致錯(cuò)誤的代碼。

? url:導(dǎo)致錯(cuò)誤的文件的 URL(如果有這個(gè)文件的話)。

另外,parseError 的 valueOf()方法返回 errorCode 的值,因此可以通過(guò)下列代碼檢測(cè)是否發(fā)

生了解析錯(cuò)誤。

if (xmldom.parseError != 0){

alert(\"Parsing error occurred.\");

}

錯(cuò)誤類型的數(shù)值編碼可能是正值,也可能是負(fù)值,因此我們只需檢測(cè)它是不是等于 0。要取得有關(guān)

解析錯(cuò)誤的詳細(xì)信息也很容易,而且可以將這些信息組合起來(lái)給出更有價(jià)值的解釋。來(lái)看下面的例子。

if (xmldom.parseError != 0){

alert(\"An error occurred:\

Error Code: \"

+ xmldom.parseError.errorCode + \"\

\"

+ \"Line: \" + xmldom.parseError.line + \"\

\"

+ \"Line Pos: \" + xmldom.parseError.linepos + \"\

\"

+ \"Reason: \" + xmldom.parseError.reason);

}

IEXmlDomExample02.htm

應(yīng)該在調(diào)用 loadXML()之后、查詢 XML 文檔之前,檢查是否發(fā)生了解析錯(cuò)誤。

1. 序列化 XML

IE 將序列化 XML 的能力內(nèi)置在了 DOM 文檔中。每個(gè) DOM 節(jié)點(diǎn)都有一個(gè) xml 屬性,其中保存著

表示該節(jié)點(diǎn)的 XML 字符串。例如:

alert(xmldom.xml);

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

第544頁(yè)

526 第 18 章 JavaScript 與 XML

文檔中的每個(gè)節(jié)點(diǎn)都支持這個(gè)簡(jiǎn)單的序列化機(jī)制,無(wú)論是序列化整個(gè)文檔還是某個(gè)子文檔樹,都非

常方便。

2. 加載 XML 文件

IE 中的 XML 文檔對(duì)象也可以加載來(lái)自服務(wù)器的文件。與 DOM3 級(jí)中的功能類似,要加載的 XML

文檔必須與頁(yè)面中運(yùn)行的 JavaScript 代碼來(lái)自同一臺(tái)服務(wù)器。同樣與 DOM3 級(jí)規(guī)范類似,加載文檔的方

式也可以分為同步和異步兩種。要指定加載文檔的方式,可以設(shè)置 async 屬性,true 表示異步,false

表示同步(默認(rèn)值為 true)。來(lái)看下面的例子。

var xmldom = createDocument();

xmldom.async = false;

在確定了加載 XML 文檔的方式后,調(diào)用 load()可以啟動(dòng)下載過(guò)程。這個(gè)方法接受一個(gè)參數(shù),即

要加載的 XML 文件的 URL。在同步方式下,調(diào)用 load()后可以立即檢測(cè)解析錯(cuò)誤并執(zhí)行相關(guān)的 XML

處理,例如:

var xmldom = createDocument();

xmldom.async = false;

xmldom.load(\"example.xml\");

if (xmldom.parseError != 0){

//處理錯(cuò)誤

} else {

alert(xmldom.documentElement.tagName); //\"root\"

alert(xmldom.documentElement.firstChild.tagName); //\"child\"

var anotherChild = xmldom.createElement(\"child\");

xmldom.documentElement.appendChild(anotherChild);

var children = xmldom.getElementsByTagName(\"child\");

alert(children.length); //2

alert(xmldom.xml);

}

IEXmlDomExample03.htm

由于是以同步方式處理 XML 文件,因此在解析完成之前,代碼不會(huì)繼續(xù)執(zhí)行,這樣的編程工作要

簡(jiǎn)單一點(diǎn)。雖然同步方式比較方便,但如果下載時(shí)間太長(zhǎng),會(huì)導(dǎo)致程序反應(yīng)很慢。因此,在加載 XML

文檔時(shí),通常都使用異步方式。

在異步加載 XML 文件的情況下,需要為 XML DOM 文檔的 onreadystatechange 事件指定處理

程序。有 4 個(gè)就緒狀態(tài)(ready state)。

? 1:DOM 正在加載數(shù)據(jù)。

? 2:DOM 已經(jīng)加載完數(shù)據(jù)。

? 3:DOM 已經(jīng)可以使用,但某些部分可能還無(wú)法訪問(wèn)。

? 4:DOM 已經(jīng)完全可以使用。

在實(shí)際開發(fā)中,要關(guān)注的只有一個(gè)就緒狀態(tài):4。這個(gè)狀態(tài)表示 XML 文件已經(jīng)全部加載完畢,而且

已經(jīng)全部解析為 DOM 文檔。通過(guò) XML 文檔的 readyState 屬性可以取得其就緒狀態(tài)。以異步方式加

載 XML 文件的典型模式如下。

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

第545頁(yè)

18.1 瀏覽器對(duì) XML DOM 的支持 527

14

2

3

17

18

13

6

7

8

9

10

11

12

var xmldom = createDocument();

xmldom.async = true;

xmldom.onreadystatechange = function(){

if (xmldom.readyState == 4){

if (xmldom.parseError != 0){

alert(\"An error occurred:\

Error Code: \"

+ xmldom.parseError.errorCode + \"\

\"

+ \"Line: \" + xmldom.parseError.line + \"\

\"

+ \"Line Pos: \" + xmldom.parseError.linepos + \"\

\"

+ \"Reason: \" + xmldom.parseError.reason);

} else {

alert(xmldom.documentElement.tagName); //\"root\"

alert(xmldom.documentElement.firstChild.tagName); //\"child\"

var anotherChild = xmldom.createElement(\"child\");

xmldom.documentElement.appendChild(anotherChild);

var children = xmldom.getElementsByTagName(\"child\");

alert(children.length); //2

alert(xmldom.xml);

}

}

};

xmldom.load(\"example.xml\");

IEXmlDomExample04.htm

要注意的是,為 onreadystatechange 事件指定處理程序的語(yǔ)句,必須放在調(diào)用 load()方法的

語(yǔ)句之前;這樣,才能確保在就緒狀態(tài)變化時(shí)調(diào)用該事件處理程序。另外,在事件處理程序內(nèi)部,還必

須注意要使用 XML 文檔變量的名稱(xmldom),不能使用 this 對(duì)象。原因是 ActiveX 控件為預(yù)防安全

問(wèn)題不允許使用 this 對(duì)象。當(dāng)文檔的就緒狀態(tài)變化為 4 時(shí),就可以放心地檢測(cè)是否發(fā)生了解析錯(cuò)誤,

并在未發(fā)生錯(cuò)誤的情況下處理 XML 了。

雖然可以通過(guò)XML DOM文檔對(duì)象加載XML文件,但公認(rèn)的還是使用XMLHttpRequest 對(duì)象比較好。有關(guān) XMLHttpRequest 對(duì)象及 Ajax 的相關(guān)內(nèi)容,將在第 21

章討論。

18.1.5 跨瀏覽器處理XML

很少有開發(fā)人員能夠有福氣專門針對(duì)一款瀏覽器做開發(fā)。因此,編寫能夠跨瀏覽器處理 XML 的函

數(shù)就成為了常見的需求。對(duì)解析 XML 而言,下面這個(gè)函數(shù)可以在所有四種主要瀏覽器中使用。

function parseXml(xml){

var xmldom = null;

if (typeof DOMParser != \"undefined\"){

xmldom = (new DOMParser()).parseFromString(xml, \"text/xml\");

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

第546頁(yè)

528 第 18 章 JavaScript 與 XML

var errors = xmldom.getElementsByTagName(\"parsererror\");

if (errors.length){

throw new Error(\"XML parsing error:\" + errors[0].textContent);

}

} else if (typeof ActiveXObject != \"undefined\"){

xmldom = createDocument();

xmldom.loadXML(xml);

if (xmldom.parseError != 0){

throw new Error(\"XML parsing error: \" + xmldom.parseError.reason);

}

} else {

throw new Error(\"No XML parser available.\");

}

return xmldom;

}

CrossBrowserXmlExample01.htm

這個(gè) parseXml()函數(shù)只接收一個(gè)參數(shù),即可解析的 XML 字符串。在函數(shù)內(nèi)部,我們通過(guò)能力檢

測(cè)來(lái)確定要使用的 XML 解析方式。DOMParser 類型是受支持最多的解決方案,因此首先檢測(cè)該類型是

否有效。如果是,則創(chuàng)建一個(gè)新的 DOMParser 對(duì)象,并將解析 XML 字符串的結(jié)果保存在變量 xmldom

中。由于 DOMParser 對(duì)象在發(fā)生解析錯(cuò)誤時(shí)不拋出錯(cuò)誤(除 IE9+之外),因此還要檢測(cè)返回的文檔以

確定解析過(guò)程是否順利。如果發(fā)現(xiàn)了解析錯(cuò)誤,則根據(jù)錯(cuò)誤消息拋出一個(gè)錯(cuò)誤。

函數(shù)的最后一部分代碼檢測(cè)了對(duì) ActiveX 的支持,并使用前面定義的 createDocument()函數(shù)來(lái)創(chuàng)

建適當(dāng)版本的 XML 文檔。與使用 DOMParser 時(shí)一樣,這里也需要檢測(cè)結(jié)果,以防有錯(cuò)誤發(fā)生。如果

確實(shí)有錯(cuò)誤發(fā)生,同樣也需要拋出一個(gè)包含錯(cuò)誤原因的錯(cuò)誤。

如果上述 XML 解析器都不可用,函數(shù)就會(huì)拋出一個(gè)錯(cuò)誤,表示無(wú)法解析了。

在使用這個(gè)函數(shù)解析 XML 字符串時(shí),應(yīng)該將它放在 try-catch 語(yǔ)句當(dāng)中,以防發(fā)生錯(cuò)誤。來(lái)看

下面的例子。

var xmldom = null;

try {

xmldom = parseXml(\"<root><child/></root>\");

} catch (ex){

alert(ex.message);

}

//進(jìn)一步處理

CrossBrowserXmlExample01.htm

對(duì)序列化 XML 而言,也可以按照同樣的方式編寫一個(gè)能夠在四大瀏覽器中運(yùn)行的函數(shù)。例如:

function serializeXml(xmldom){

if (typeof XMLSerializer != \"undefined\"){

return (new XMLSerializer()).serializeToString(xmldom);

} else if (typeof xmldom.xml != \"undefined\"){

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

第547頁(yè)

18.2 瀏覽器對(duì) XPath 的支持 529

14

2

3

17

18

13

6

7

8

9

10

11

12

return xmldom.xml;

} else {

throw new Error(\"Could not serialize XML DOM.\");

}

}

CrossBrowserXmlExample02.htm

這個(gè) serializeXml()函數(shù)接收一個(gè)參數(shù),即要序列化的 XML DOM 文檔。與 parseXml()函數(shù)

一樣,這個(gè)函數(shù)首先也是檢測(cè)受到最廣泛支持的特性,即 XMLSerializer。如果這個(gè)類型有效,則使

用它來(lái)生成并返回文檔的 XML 字符串。由于 ActiveX 方案比較簡(jiǎn)單,只使用了一個(gè) xml 屬性,因此這

個(gè)函數(shù)直接檢測(cè)了該屬性。如果上述兩方面嘗試都失敗了,函數(shù)就會(huì)拋出一個(gè)錯(cuò)誤,說(shuō)明序列化不能進(jìn)

行。一般來(lái)說(shuō),只要針對(duì)瀏覽器使用了適當(dāng)?shù)?XML DOM 對(duì)象,就不會(huì)出現(xiàn)無(wú)法序列化的情況,因而也

就沒有必要在 try-catch 語(yǔ)句中調(diào)用 serializeXml()。結(jié)果,就只需如下一行代碼即可:

var xml = serializeXml(xmldom);

只不過(guò)由于序列化過(guò)程的差異,相同的 DOM 對(duì)象在不同的瀏覽器下,有可能會(huì)得到不同的 XML

字符串。

18.2 瀏覽器對(duì) XPath 的支持

XPath 是設(shè)計(jì)用來(lái)在 DOM 文檔中查找節(jié)點(diǎn)的一種手段,因而對(duì) XML 處理也很重要。但是,DOM3

級(jí)以前的標(biāo)準(zhǔn)并沒有就 XPath 的 API 作出規(guī)定;XPath 是在 DOM3 級(jí) XPath 模塊中首次躋身推薦標(biāo)準(zhǔn)行

列的。很多瀏覽器都實(shí)現(xiàn)了這個(gè)推薦標(biāo)準(zhǔn),但 IE 則以自己的方式實(shí)現(xiàn)了 XPath。

18.2.1 DOM3 級(jí)XPath

DOM3級(jí) XPath規(guī)范定義了在 DOM中對(duì) XPath表達(dá)式求值的接口。要確定某瀏覽器是否支持 DOM3

級(jí) XPath,可以使用以下 JavaScript 代碼:

var supportsXPath = document.implementation.hasFeature(\"XPath\", \"3.0\");

在 DOM3 級(jí) XPath 規(guī)范定義的類型中,最重要的兩個(gè)類型是 XPathEvaluator 和 XPathResult。

XPathEvaluator 用于在特定的上下文中對(duì) XPath 表達(dá)式求值。這個(gè)類型有下列 3 個(gè)方法。

? createExpression(expression, nsresolver):將 XPath 表達(dá)式及相應(yīng)的命名空間信息轉(zhuǎn)

換成一個(gè) XPathExpression,這是查詢的編譯版。在多次使用同一個(gè)查詢時(shí)很有用。

? createNSResolver(node):根據(jù) node 的命名空間信息創(chuàng)建一個(gè)新的 XPathNSResolver 對(duì)

象。在基于使用命名空間的 XML 文檔求值時(shí),需要使用 XPathNSResolver 對(duì)象。

? evaluate(expression, context, nsresolver, type, result):在給定的上下文中,

基于特定的命名空間信息來(lái)對(duì) XPath 表達(dá)式求值。剩下的參數(shù)指定如何返回結(jié)果。

在 Firefox、Safari、Chrome 和 Opera 中,Document 類型通常都是與 XPathEvaluator 接口一起實(shí)

現(xiàn)的。換句話說(shuō),在這些瀏覽器中,既可以創(chuàng)建 XPathEvaluator 的新實(shí)例,也可以使用 Document

實(shí)例中的方法(XML 或 HTML 文檔均是如此)。

在上面這三個(gè)方法中,evaluate()是最常用的。這個(gè)方法接收 5 個(gè)參數(shù):XPath 表達(dá)式、上下文

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

第548頁(yè)

530 第 18 章 JavaScript 與 XML

節(jié)點(diǎn)、命名空間求解器、返回結(jié)果的類型和保存結(jié)果的 XPathResult 對(duì)象(通常是 null,因?yàn)榻Y(jié)果

也會(huì)以函數(shù)值的形式返回)。其中,第三個(gè)參數(shù)(命名空間求解器)只在 XML 代碼中使用了 XML 命名

空間時(shí)有必要指定;如果 XML 代碼中沒有使用命名空間,則這個(gè)參數(shù)應(yīng)該指定為 null。第四個(gè)參數(shù)(返

回結(jié)果的類型)的取值范圍是下列常量之一。

? XPathResult.ANY_TYPE:返回與 XPath 表達(dá)式匹配的數(shù)據(jù)類型。

? XPathResult.NUMBER_TYPE:返回?cái)?shù)值。

? XPathResult.STRING_TYPE:返回字符串值。

? XPathResult.BOOLEAN_TYPE:返回布爾值。

? XPathResult.UNORDERED_NODE_ITERATOR_TYPE:返回匹配的節(jié)點(diǎn)集合,但集合中節(jié)點(diǎn)的次

序不一定與它們?cè)谖臋n中的次序一致。

? XPathResult.ORDERED_NODE_ITERATOR_TYPE:返回匹配的節(jié)點(diǎn)集合,集合中節(jié)點(diǎn)的次序與

它們?cè)谖臋n中的次序一致。這是最常用的結(jié)果類型。

? XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:返回節(jié)點(diǎn)集合的快照,由于是在文檔外部

捕獲節(jié)點(diǎn),因此對(duì)文檔的后續(xù)操作不會(huì)影響到這個(gè)節(jié)點(diǎn)集合。集合中節(jié)點(diǎn)的次序不一定與它們

在文檔中的次序一致。

? XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:返回節(jié)點(diǎn)集合的快照,由于是在文檔外部捕

獲節(jié)點(diǎn),因此對(duì)文檔的后續(xù)操作不會(huì)影響到這個(gè)節(jié)點(diǎn)集合。集合中節(jié)點(diǎn)的次序與它們?cè)谖臋n中

的次序一致。

? XPathResult.ANY_UNORDERED_NODE_TYPE:返回匹配的節(jié)點(diǎn)集合,但集合中節(jié)點(diǎn)的次序不

一定與它們?cè)谖臋n中的次序一致。

? XPathResult.FIRST_ORDERED_NODE_TYPE:返回只包含一個(gè)節(jié)點(diǎn)的節(jié)點(diǎn)集合,包含的這個(gè)

節(jié)點(diǎn)就是文檔中第一個(gè)匹配的節(jié)點(diǎn)。

指定的結(jié)果類型決定了如何取得結(jié)果的值。下面來(lái)看一個(gè)典型的例子。

var result = xmldom.evaluate(\"employee/name\", xmldom.documentElement, null,

XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);

if (result !== null) {

var node = result.iterateNext();

while(node) {

alert(node.tagName);

node = node.iterateNext();

}

}

DomXPathExample01.htm

這個(gè)例子中為返回結(jié)果指定的是 XPathResult.ORDERED_NODE_ITERATOR_TYPE,也是最常用的

結(jié)果類型。如果沒有節(jié)點(diǎn)匹配 XPath 表達(dá)式,evaluate()返回 null;否則,它會(huì)返回一個(gè) XPathResult

對(duì)象。這個(gè) XPathResult 對(duì)象帶有的屬性和方法,可以用來(lái)取得特定類型的結(jié)果。如果節(jié)點(diǎn)是一個(gè)節(jié)

點(diǎn)迭代器,無(wú)論是次序一致還是次序不一致的,都必須要使用 iterateNext()方法從節(jié)點(diǎn)中取得匹配

的節(jié)點(diǎn)。在沒有更多的匹配節(jié)點(diǎn)時(shí),iterateNext()返回 null。

如果指定的是快照結(jié)果類型(不管是次序一致還是次序不一致的),就必須使用 snapshotItem()

方法和 snapshotLength 屬性,例如:

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

第549頁(yè)

18.2 瀏覽器對(duì) XPath 的支持 531

14

2

3

17

18

13

6

7

8

9

10

11

12

var result = xmldom.evaluate(\"employee/name\", xmldom.documentElement, null,

XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

if (result !== null) {

for (var i=0, len=result.snapshotLength; i < len; i++) {

alert(result.snapshotItem(i).tagName);

}

}

DomXPathExample02.htm

這里,snapshotLength 返回的是快照中節(jié)點(diǎn)的數(shù)量,而 snapshotItem()則返回快照中給定位

置的節(jié)點(diǎn)(與 NodeList 中的 length 和 item()相似)。

1. 單節(jié)點(diǎn)結(jié)果

指定常量 XPathResult.FIRST_ORDERED_NODE_TYPE 會(huì)返回第一個(gè)匹配的節(jié)點(diǎn),可以通過(guò)結(jié)果

的 singleNodeValue 屬性來(lái)訪問(wèn)該節(jié)點(diǎn)。例如:

var result = xmldom.evaluate(\"employee/name\", xmldom.documentElement, null,

XPathResult.FIRST_ORDERED_NODE_TYPE, null);

if (result !== null) {

alert(result.singleNodeValue.tagName);

}

DomXPathExample03.htm

與前面的查詢一樣,在沒有匹配節(jié)點(diǎn)的情況下,evaluate()返回 null。如果有節(jié)點(diǎn)返回,那么就

可以通過(guò) singleNodeValue 屬性來(lái)訪問(wèn)它。

2. 簡(jiǎn)單類型結(jié)果

通過(guò) XPath 也可以取得簡(jiǎn)單的非節(jié)點(diǎn)數(shù)據(jù)類型,這時(shí)候就要使用 XPathResult 的布爾值、數(shù)值和

字符串類型了。這幾個(gè)結(jié)果類型分別會(huì)通過(guò) booleanValue、numberValue 和 stringValue 屬性返

回一個(gè)值。對(duì)于布爾值類型,如果至少有一個(gè)節(jié)點(diǎn)與 XPath 表達(dá)式匹配,則求值結(jié)果返回 true,否則

返回 false。來(lái)看下面的例子。

var result = xmldom.evaluate(\"employee/name\", xmldom.documentElement, null,

XPathResult.BOOLEAN_TYPE, null);

alert(result.booleanValue);

DomXPathExample04.htm

在這個(gè)例子中,如果有節(jié)點(diǎn)匹配\"employee/name\",則 booleanValue 屬性的值就是 true。

對(duì)于數(shù)值類型,必須在 XPath 表達(dá)式參數(shù)的位置上指定一個(gè)能夠返回?cái)?shù)值的 XPath 函數(shù),例如計(jì)算

與給定模式匹配的所有節(jié)點(diǎn)數(shù)量的 count()。來(lái)看下面的例子。

var result = xmldom.evaluate(\"count(employee/name)\", xmldom.documentElement,

null, XPathResult.NUMBER_TYPE, null);

alert(result.numberValue);

DomXPathExample05.htm

以上代碼會(huì)輸出與\"employee/name\"匹配的節(jié)點(diǎn)數(shù)量(即 2)。如果使用這個(gè)方法的時(shí)候沒有指定

與前例類似的 XPath 函數(shù),那么 numberValue 的值將等于 NaN。

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

第550頁(yè)

532 第 18 章 JavaScript 與 XML

對(duì)于字符串類型,evaluate()方法會(huì)查找與 XPath 表達(dá)式匹配的第一個(gè)節(jié)點(diǎn),然后返回其第一個(gè)

子節(jié)點(diǎn)的值(實(shí)際上是假設(shè)第一個(gè)子節(jié)點(diǎn)為文本節(jié)點(diǎn))。如果沒有匹配的節(jié)點(diǎn),結(jié)果就是一個(gè)空字符串。

來(lái)看一個(gè)例子。

var result = xmldom.evaluate(\"employee/name\", xmldom.documentElement, null,

XPathResult.STRING_TYPE, null);

alert(result.stringValue);

DomXPathExample06.htm

這個(gè)例子的輸出結(jié)果中包含著與\"element/name\"匹配的第一個(gè)元素的第一個(gè)子節(jié)點(diǎn)中包含的字

符串。

3. 默認(rèn)類型結(jié)果

所有 XPath 表達(dá)式都會(huì)自動(dòng)映射到特定的結(jié)果類型。像前面那樣設(shè)置特定的結(jié)果類型,可以限制表

達(dá)式的輸出。而使用 XPathResult.ANY_TYPE 常量可以自動(dòng)確定返回結(jié)果的類型。一般來(lái)說(shuō),自動(dòng)選

擇的結(jié)果類型可能是布爾值、數(shù)值、字符串值或一個(gè)次序不一致的節(jié)點(diǎn)迭代器。要確定返回的是什么結(jié)

果類型,可以檢測(cè)結(jié)果的 resultType 屬性,如下面的例子所示。

var result = xmldom.evaluate(\"employee/name\", xmldom.documentElement, null,

XPathResult.ANY_TYPE, null);

if (result !== null) {

switch(result.resultType) {

case XPathResult.STRING_TYPE:

//處理字符串類型

break;

case XPathResult.NUMBER_TYPE:

//處理數(shù)值類型

break;

case XPathResult.BOOLEAN_TYPE:

//處理布爾值類型

break;

case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:

//處理次序不一致的節(jié)點(diǎn)迭代器類型

break;

default:

//處理其他可能的結(jié)果類型

}

}

顯然,XPathResult.ANY_TYPE 可以讓我們更靈活地使用 XPath,但是卻要求有更多的處理代碼

來(lái)處理返回的結(jié)果。

4. 命名空間支持

對(duì)于利用了命名空間的 XML 文檔,XPathEvaluator 必須知道命名空間信息,然后才能正確地進(jìn)

行求值。處理命名空間的方法也不止一種。我們以下面的 XML 代碼為例。

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

百萬(wàn)用戶使用云展網(wǎng)進(jìn)行書冊(cè)翻頁(yè)效果制作,只要您有文檔,即可一鍵上傳,自動(dòng)生成鏈接和二維碼(獨(dú)立電子書),支持分享到微信和網(wǎng)站!
收藏
轉(zhuǎn)發(fā)
下載
免費(fèi)制作
其他案例
更多案例
免費(fèi)制作
x
{{item.desc}}
下載
{{item.title}}
{{toast}}