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

javascript-gaojichengx有目錄u

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

javascript-gaojichengx有目錄u

9.3 用戶代理檢測 233 1 2 3 45 13 67 8 9 1011 12 //呈現(xiàn)引擎 ie: 0, gecko: 0, webkit: 0, khtml: 0, opera: 0, //具體的版本 ver: null }; var browser = { //瀏覽器 ie: 0, firefox: 0, safari: 0, konq: 0, opera: 0, chrome: 0, //具體的版本 ver: null }; //在此檢測呈現(xiàn)引擎、平臺和設(shè)備 return { engine: engine, browser: browser }; }(); 代碼中又添加了私有變量 browser,用于保存每個主要瀏覽器的屬性。與 engine 變量一樣,除了當前使用的瀏覽器,其他屬性的值將保持為 0;如果是當前使用的瀏覽器,則這個屬性中保存的是浮點數(shù)值形式的版本號。同樣,ver 屬性中在必要時將會包含字符串形式的瀏覽器完整版本號。由于大多數(shù)瀏覽器與其呈現(xiàn)引擎密切相關(guān),所以下面示例中檢測瀏覽器的代碼與檢測呈現(xiàn)引擎的代碼是混合在一起的。//... [收起]
[展開]
javascript-gaojichengx有目錄u
粉絲: {{bookData.followerCount}}
文本內(nèi)容
第251頁

9.3 用戶代理檢測 233

1

2

3

4

5

13

6

7

8

9

10

11

12

//呈現(xiàn)引擎

ie: 0,

gecko: 0,

webkit: 0,

khtml: 0,

opera: 0,

//具體的版本

ver: null

};

var browser = {

//瀏覽器

ie: 0,

firefox: 0,

safari: 0,

konq: 0,

opera: 0,

chrome: 0,

//具體的版本

ver: null

};

//在此檢測呈現(xiàn)引擎、平臺和設(shè)備

return {

engine: engine,

browser: browser

};

}();

代碼中又添加了私有變量 browser,用于保存每個主要瀏覽器的屬性。與 engine 變量一樣,除

了當前使用的瀏覽器,其他屬性的值將保持為 0;如果是當前使用的瀏覽器,則這個屬性中保存的是浮

點數(shù)值形式的版本號。同樣,ver 屬性中在必要時將會包含字符串形式的瀏覽器完整版本號。由于大

多數(shù)瀏覽器與其呈現(xiàn)引擎密切相關(guān),所以下面示例中檢測瀏覽器的代碼與檢測呈現(xiàn)引擎的代碼是混合

在一起的。

//檢測呈現(xiàn)引擎及瀏覽器

var ua = navigator.userAgent;

if (window.opera){

engine.ver = browser.ver = window.opera.version();

engine.opera = browser.opera = parseFloat(engine.ver);

} else if (/AppleWebKit\\/(\\S+)/.test(ua)){

engine.ver = RegExp[\"$1\"];

engine.webkit = parseFloat(engine.ver);

//確定是 Chrome 還是 Safari

if (/Chrome\\/(\\S+)/.test(ua)){

browser.ver = RegExp[\"$1\"];

browser.chrome = parseFloat(browser.ver);

} else if (/Version\\/(\\S+)/.test(ua)){

browser.ver = RegExp[\"$1\"];

browser.safari = parseFloat(browser.ver);

} else {

//近似地確定版本號

var safariVersion = 1;

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

第252頁

234 第 9 章 客戶端檢測

if (engine.webkit < 100){

safariVersion = 1;

} else if (engine.webkit < 312){

safariVersion = 1.2;

} else if (engine.webkit < 412){

safariVersion = 1.3;

} else {

safariVersion = 2;

}

browser.safari = browser.ver = safariVersion;

}

} else if (/KHTML\\/(\\S+)/.test(ua) || /Konqueror\\/([^;]+)/.test(ua)){

engine.ver = browser.ver = RegExp[\"$1\"];

engine.khtml = browser.konq = parseFloat(engine.ver);

} else if (/rv:([^\\)]+)\\) Gecko\\/\\d{8}/.test(ua)){

engine.ver = RegExp[\"$1\"];

engine.gecko = parseFloat(engine.ver);

//確定是不是 Firefox

if (/Firefox\\/(\\S+)/.test(ua)){

browser.ver = RegExp[\"$1\"];

browser.firefox = parseFloat(browser.ver);

}

} else if (/MSIE ([^;]+)/.test(ua)){

engine.ver = browser.ver = RegExp[\"$1\"];

engine.ie = browser.ie = parseFloat(engine.ver);

}

對 Opera 和 IE 而言,browser 對象中的值等于 engine 對象中的值。對 Konqueror 而言,browser.

konq 和 browser.ver 屬性分別等于 engine.khtml 和 engine.ver 屬性。

為了檢測 Chrome 和 Safari,我們在檢測引擎的代碼中添加了 if 語句。提取 Chrome 的版本號時,需

要查找字符串\"Chrome/\"并取得該字符串后面的數(shù)值。而提取 Safari 的版本號時,則需要查找字符串

\"Version/\"并取得其后的數(shù)值。由于這種方式僅適用于 Safari 3 及更高版本,因此需要一些備用的代

碼,將 WebKit 的版本號近似地映射為 Safari 的版本號(參見上一小節(jié)中的表格)。

在檢測 Firefox 的版本時,首先要找到字符串\"Firefox/\",然后提取出該字符串后面的數(shù)值(即版

本號)。當然,只有呈現(xiàn)引擎被判別為 Gecko 時才會這樣做。

有了上面這些代碼之后,我們就可以編寫下面的邏輯。

if (client.engine.webkit) { //if it’s WebKit

if (client.browser.chrome){

//執(zhí)行針對 Chrome 的代碼

} else if (client.browser.safari){

//執(zhí)行針對 Safari 的代碼

}

} else if (client.engine.gecko){

if (client.browser.firefox){

//執(zhí)行針對 Firefox 的代碼

} else {

//執(zhí)行針對其他 Gecko 瀏覽器的代碼

}

}

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

第253頁

9.3 用戶代理檢測 235

1

2

3

4

5

13

6

7

8

9

10

11

12

3. 識別平臺

很多時候,只要知道呈現(xiàn)引擎就足以編寫出適當?shù)拇a了。但在某些條件下,平臺可能是必須關(guān)注

的問題。那些具有各種平臺版本的瀏覽器(如 Safari、Firefox 和 Opera)在不同的平臺下可能會有不同

的問題。目前的三大主流平臺是 Windows、Mac 和 Unix(包括各種 Linux)。為了檢測這些平臺,還需

要像下面這樣再添加一個新對象。

var client = function(){

var engine = {

//呈現(xiàn)引擎

ie: 0,

gecko: 0,

webkit: 0,

khtml: 0,

opera: 0,

//具體的版本號

ver: null

};

var browser = {

//瀏覽器

ie: 0,

firefox: 0,

safari: 0,

konq: 0,

opera: 0,

chrome: 0,

//具體的版本號

ver: null

};

var system = {

win: false,

mac: false,

x11: false

};

//在此檢測呈現(xiàn)引擎、平臺和設(shè)備

return {

engine: engine,

browser: browser,

system: system

};

}();

顯然,上面的代碼中又添加了一個包含 3 個屬性的新變量 system。其中,win 屬性表示是否為

Windows 平臺,mac 表示 Mac,而 x11 表示 Unix。與呈現(xiàn)引擎不同,在不能訪問操作系統(tǒng)或版本的

情況下,平臺信息通常是很有限的。對這三個平臺而言,瀏覽器一般只報告 Windows 版本。為此,新

變量 system 的每個屬性最初都保存著布爾值 false,而不是像呈現(xiàn)引擎屬性那樣保存著數(shù)字值。

在確定平臺時,檢測 navigator.platform 要比檢測用戶代理字符串更簡單,后者在不同瀏覽器

中會給出不同的平臺信息。而 navigator.platform 屬性可能的值包括\"Win32\"、\"Win64\"、

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

第254頁

236 第 9 章 客戶端檢測

\"MacPPC\"、\"MacIntel\"、\"X11\"和\"Linux i686\",這些值在不同的瀏覽器中都是一致的。檢測平臺

的代碼非常直觀,如下所示:

var p = navigator.platform;

system.win = p.indexOf(\"Win\") == 0;

system.mac = p.indexOf(\"Mac\") == 0;

system.x11 = (p.indexOf(\"X11\") == 0) || (p.indexOf(\"Linux\") == 0);

以上代碼使用 indexOf()方法來查找平臺字符串的開始位置。雖然\"Win32\"是當前瀏覽器唯一支

持的 Windows 字符串,但隨著向 64 位 Windows 架構(gòu)的遷移,將來很可能會出現(xiàn)\"Win64\"平臺信息值。

為了對此有所準備,檢測平臺的代碼中查找的只是字符串\"Win\"的開始位置。而檢測 Mac 平臺的方式也

類似,同樣是考慮到了 MacPPC 和 MacIntel。在檢測 Unix 時,則同時檢查了字符串\"X11\"和\"Linux\"

在平臺字符串中的開始位置,從而確保了代碼能夠向前兼容其他變體。

Gecko的早期版本在所有 Windows平臺中都返回字符串\"Windows\",在所有 Mac平

臺中則都返回字符串\"Macintosh\"。不過,這都是 Firefox 1發(fā)布以前的事了,F(xiàn)irefox 1

確定了 navigator.platform 的值。

4. 識別 Windows 操作系統(tǒng)

在 Windows 平臺下,還可以從用戶代理字符串中進一步取得具體的操作系統(tǒng)信息。在 Windows XP

之前,Windows 有兩種版本,分別針對家庭用戶和商業(yè)用戶。針對家庭用戶的版本分別是 Windows 95、

98 和 Windows ME。而針對商業(yè)用戶的版本則一直叫做 Window NT,最后由于市場原因改名為 Windows

2000。這兩個產(chǎn)品線后來又合并成一個由 Windows NT 發(fā)展而來的公共的代碼基,代表產(chǎn)品就是 Windows

XP。隨后,微軟在 Windows XP 基礎(chǔ)上又構(gòu)建了 Windows Vista。

只有了解這些信息,才能搞清楚用戶代理字符串中 Windows 操作系統(tǒng)的具體版本。下表列出了不同

瀏覽器在表示不同的 Windows 操作系統(tǒng)時給出的不同字符串。

Windows版本 IE 4+ Gecko Opera < 7 Opera 7+ WebKit

95 \"Windows 95\" \"Win95\" \"Windows 95\" \"Windows 95\" n/a

98 \"Windows 98\" \"Win98\" \"Windows 98\" \"Windows 98\" n/a

NT 4.0 \"Windows NT\" \"WinNT4.0\" \"Windows NT 4.0\" \"Windows NT 4.0\" n/a

2000 \"Windows NT 5.0\" \"Windows NT 5.0\" \"Windows 2000\" \"Windows NT 5.0\" n/a

ME \"Win 9x 4.90\" \"Win 9x 4.90\" \"Windows ME\" \"Win 9x 4.90\" n/a

XP \"Windows NT 5.1\" \"Windows NT 5.1\" \"Windows XP\" \"Windows NT 5.1\" \"Windows NT 5.1\"

Vista \"Windows NT 6.0\" \"Windows NT 6.0\" n/a \"Windows NT 6.0\" \"Windows NT 6.0\"

7 \"Windows NT 6.1\" \"Windows NT 6.1\" n/a \"Windows NT 6.1\" \"Windows NT 6.1\"

由于用戶代理字符串中的 Windows 操作系統(tǒng)版本表示方法各異,因此檢測代碼并不十分直觀。好在,

從 Windows 2000 開始,表示操作系統(tǒng)的字符串大部分都還相同,只有版本號有變化。為了檢測不同的

Windows 操作系統(tǒng),必須要使用正則表達式。由于使用 Opera 7 之前版本的用戶已經(jīng)不多了,因此我們

可以忽略這部分瀏覽器。

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

第255頁

9.3 用戶代理檢測 237

1

2

3

4

5

13

6

7

8

9

10

11

12

第一步就是匹配 Windows 95 和 Windows 98 這兩個字符串。對這兩個字符串,只有 Gecko 與其他瀏

覽器不同,即沒有\"dows\",而且\"Win\"與版本號之間沒有空格。要匹配這個模式,可以使用下面這個簡

單的正則表達式。

/Win(?:dows )?([^do]{2})/

這個正則表達式中的捕獲組會返回操作系統(tǒng)的版本。由于版本可能是任何兩個字符編碼(例如 95、

98、9x、NT、ME 及 XP),因此要使用兩個非空格字符。

Gecko 在表示 Windows NT 時會在末尾添加\"4.0\",與其查找實際的字符串,不如像下面這樣查找

小數(shù)值更合適。

/Win(?:dows )?([^do]{2})(\\d+\\.\\d+)?/

這樣,正則表達式中就包含了第二個捕獲組,用于取得 NT 的版本號。由于該版本號對于 Windows

95 和 Windows 98 而言是不存在的,所以必須設(shè)置為可選。這個模式與 Opera 表示 Windows NT 的字符

串之間唯一的區(qū)別,就是\"NT\"與\"4.0\"之間的空格,這在模式中很容易添加。

/Win(?:dows )?([^do]{2})\\s?(\\d+\\.\\d+)?/

經(jīng)過一番修改之后,這個正則表達式也可以成功地匹配 Windows ME、Windows XP 和 Windows Vista

的字符串了。具體來說,第一個捕獲組將會匹配 95、98、9x、NT、ME 或 XP。第二個捕獲組則只針對

Windows ME 及所有 Windows NT 的變體。這個信息可以作為具體的操作系統(tǒng)信息保存在 system.win

屬性中,如下所示。

if (system.win){

if (/Win(?:dows )?([^do]{2})\\s?(\\d+\\.\\d+)?/.test(ua)){

if (RegExp[\"$1\"] == \"NT\"){

switch(RegExp[\"$2\"]){

case \"5.0\":

system.win = \"2000\";

break;

case \"5.1\":

system.win = \"XP\";

break;

case \"6.0\":

system.win = \"Vista\";

break;

case \"6.1\":

system.win = \"7\";

break;

default:

system.win = \"NT\";

break;

}

} else if (RegExp[\"$1\"] == \"9x\"){

system.win = \"ME\";

} else {

system.win = RegExp[\"$1\"];

}

}

}

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

第256頁

238 第 9 章 客戶端檢測

如果 system.win 的值為 true,那么就使用這個正則表達式從用戶代理字符串中提取具體的信息。

鑒于 Windows 將來的某個版本也許不能使用這個方法來檢測,所以第一步應該先檢測用戶代理字符串是

否與這個模式匹配。在模式匹配的情況下,第一個捕獲組中可能會包含\"95\"、\"98\"、\"9x\"或\"NT\"。如

果這個值是\"NT\",可以將 system.win 設(shè)置為相應操作系統(tǒng)的字符串;如果是\"9x\",那么 system.win

就要設(shè)置成\"ME\";如果是其他值,則將所捕獲的值直接賦給 system.win。有了這些檢測平臺的代碼后,

我們就可以編寫如下代碼。

if (client.system.win){

if (client.system.win == \"XP\") {

//說明是 XP

} else if (client.system.win == \"Vista\"){

//說明是 Vista

}

}

由于非空字符串會轉(zhuǎn)換為布爾值 true,因此可以將 client.system.win 作為布爾值用在 if 語

句中。而在需要更多有關(guān)操作系統(tǒng)的信息時,則可以使用其中保存的字符串值。

5. 識別移動設(shè)備

2006 年到 2007 年,移動設(shè)備中 Web 瀏覽器的應用呈爆炸性增長。四大主要瀏覽器都推出了手機

版和在其他設(shè)備中運行的版本。要檢測相應的設(shè)備,第一步是為要檢測的所有移動設(shè)備添加屬性,如

下所示。

var client = function(){

var engine = {

//呈現(xiàn)引擎

ie: 0,

gecko: 0,

webkit: 0,

khtml: 0,

opera: 0,

//具體的版本號

ver: null

};

var browser = {

//瀏覽器

ie: 0,

firefox: 0,

safari: 0,

konq: 0,

opera: 0,

chrome: 0,

//具體的版本號

ver: null

};

var system = {

win: false,

mac: false,

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

第257頁

9.3 用戶代理檢測 239

1

2

3

4

5

13

6

7

8

9

10

11

12

x11: false,

//移動設(shè)備

iphone: false,

ipod: false,

ipad: false,

ios: false,

android: false,

nokiaN: false,

winMobile: false };

//在此檢測呈現(xiàn)引擎、平臺和設(shè)備

return {

engine: engine,

browser: browser,

system: system

};

}();

然后,通常簡單地檢測字符串\"iPhone\"、\"iPod\"和\"iPad\",就可以分別設(shè)置相應屬性的值了。

system.iphone = ua.indexOf(\"iPhone\") > -1;

system.ipod = ua.indexOf(\"iPod\") > -1;

system.ipod = ua.indexOf(\"iPad\") > -1;

除了知道 iOS設(shè)備,最好還能知道 iOS的版本號。在 iOS 3之前,用戶代理字符串中只包含\"CPU like

Mac OS\",后來 iPhone 中又改成\"CPU iPhone OS 3_0 like Mac OS X\",iPad 中又改成\"CPU OS 3_2

like Mac OS X\"。也就是說,檢測 iOS 需要正則表達式反映這些變化。

//檢測 iOS 版本

if (system.mac && ua.indexOf(\"Mobile\") > -1){

if (/CPU (?:iPhone )?OS (\\d+_\\d+)/.test(ua)){

system.ios = parseFloat(RegExp.$1.replace(\"_\", \".\"));

} else {

system.ios = 2; //不能真正檢測出來,所以只能猜測

}

}

檢查系統(tǒng)是不是 Mac OS、字符串中是否存在\"Mobile\",可以保證無論是什么版本,system.ios

中都不會是 0。然后,再使用正則表達式確定是否存在 iOS 的版本號。如果有,將 system.ios 設(shè)置為

表示版本號的浮點值;否則,將版本設(shè)置為 2。(因為沒有辦法確定到底是什么版本,所以設(shè)置為更早的

版本比較穩(wěn)妥。)

檢測 Android 操作系統(tǒng)也很簡單,也就是搜索字符串\"Android\"并取得緊隨其后的版本號。

//檢測 Android 版本

if (/Android (\\d+\\.\\d+)/.test(ua)){

system.android = parseFloat(RegExp.$1);

}

由于所有版本的 Android 都有版本值,因此這個正則表達式可以精確地檢測所有版本,并將

system.android 設(shè)置為正確的值。

諾基亞 N 系列手機使用的也是 WebKit,其用戶代理字符串與其他基于 WebKit 的手機很相似,例如:

Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 NokiaN95/11.0.026; Profile MIDP-2.0

Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413

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

第258頁

240 第 9 章 客戶端檢測

雖然諾基亞 N 系列手機在用戶代理字符串中聲稱使用的是\"Safari\",但實際上并不是 Safari,盡

管確實是基于 WebKit 引擎。只要像下面檢測一下用戶代理字符串中是否存在\"NokiaN\",就足以確定是

不是該系列的手機了。

system.nokiaN = ua.indexOf(\"NokiaN\") > -1;

在了解這些設(shè)備信息的基礎(chǔ)上,就可以通過下列代碼來確定用戶使用的是什么設(shè)備中的 WebKit 來

訪問網(wǎng)頁:

if (client.engine.webkit){

if (client.system. iOS){

//iOS 手機的內(nèi)容

} else if (client.system.android){

//Android 手機的內(nèi)容

} else if (client.system.nokiaN){

//諾基亞手機的內(nèi)容

}

}

最后一種主要的移動設(shè)備平臺是 Windows Mobile(也稱為 Windows CE),用于 Pocket PC 和

Smartphone 中。由于從技術(shù)上說這些平臺都屬于 Windows 平臺,因此 Windows 平臺和操作系統(tǒng)都會返

回正確的值。對于 Windows Mobile 5.0 及以前版本,這兩種設(shè)備的用戶代理字符串非常相似,如下所示:

Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320)

Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Smartphone; 176x220)

第一個來自 Pocket PC 中的移動 Internet Explorer 4.01,第二個來自 Smartphone 中的同一個瀏覽器。

當 Windows 操作系統(tǒng)檢測腳本檢測這兩個字符串時,system.win 將被設(shè)置為\"CE\",因此在檢測

Windows Mobile 時可以使用這個值:

system.winMobile = (system.win == \"CE\");

不建議測試字符串中的\"PPC\"或\"Smartphone\",因為在 Windows Mobile 5.0 以后版本的瀏覽器中,

這些記號已經(jīng)被移除了。不過,一般情況下,只知道某個設(shè)備使用的是 Windows Mobile 也就足夠了。

Windows Phone 7 的用戶代理字符串稍有改進,基本格式如下:

Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0)

Asus;Galaxy6

其中,Windows 操作符的標識符與已往完全不同,因此在這個用戶代理中 client.system.win

等于\"Ph\"。從中可以取得有關(guān)系統(tǒng)的更多信息:

//windows mobile

if (system.win == \"CE\"){

system.winMobile = system.win;

} else if (system.win == \"Ph\"){

if(/Windows Phone OS (\\d+.\\d+)/.test(ua)){;

system.win = \"Phone\";

system.winMobile = parseFloat(RegExp[\"$1\"]);

}

}

如果 system.win 的值是\"CE\",就說明是老版本的 Windows Mobile,因此 system.winMobile

會被設(shè)置為相同的值(只能知道這個信息)。如果 system.win 的值是\"Ph\",那么這個設(shè)備就可能是

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

第259頁

9.3 用戶代理檢測 241

1

2

3

4

5

13

6

7

8

9

10

11

12

Windows Phone 7 或更新版本。因此就用正則表達式來測試格式并提取版本號,將 system.win 的值重

置為\"Phone\",而將 system.winMobile 設(shè)置為版本號。

6. 識別游戲系統(tǒng)

除了移動設(shè)備之外,視頻游戲系統(tǒng)中的 Web 瀏覽器也開始日益普及。任天堂 Wii 和 Playstation 3 或

者內(nèi)置 Web 瀏覽器,或者提供了瀏覽器下載。Wii 中的瀏覽器實際上是定制版的 Opera,是專門為 Wii

Remote 設(shè)計的。Playstation 的瀏覽器是自己開發(fā)的,沒有基于前面提到的任何呈現(xiàn)引擎。這兩個瀏覽器

中的用戶代理字符串如下所示:

Opera/9.10 (Nintendo Wii;U; ; 1621; en)

Mozilla/5.0 (PLAYSTATION 3; 2.00)

第一個字符串來自運行在 Wii 中的 Opera,它忠實地繼承了 Opera 最初的用戶代理字符串格式(Wii

上的 Opera 不具備隱瞞身份的能力)。第二個字符串來自 Playstation3,雖然它為了兼容性而將自己標識

為 Mozilla 5.0,但并沒有給出太多信息。而且,設(shè)備名稱居然全部使用了大寫字母,讓人覺得很奇怪;

強烈希望將來的版本能夠改變這種情況。

在檢測這些設(shè)備以前,我們必須先為 client.system 中添加適當?shù)膶傩?,如下所示?/p>

var client = function(){

var engine = {

//呈現(xiàn)引擎

ie: 0,

gecko: 0,

webkit: 0,

khtml: 0,

opera: 0,

//具體的版本號

ver: null

};

var browser = {

//瀏覽器

ie: 0,

firefox: 0,

safari: 0,

konq: 0,

opera: 0,

chrome: 0,

//具體的版本號

ver: null

};

var system = {

win: false,

mac: false,

x11: false,

//移動設(shè)備

iphone: false,

ipod: false,

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

第260頁

242 第 9 章 客戶端檢測

ipad: false,

ios: false,

android: false,

nokiaN: false,

winMobile: false,

//游戲系統(tǒng)

wii: false,

ps: false

};

//在此檢測呈現(xiàn)引擎、平臺和設(shè)備

return {

engine: engine,

browser: browser,

system: system

};

}();

檢測前述游戲系統(tǒng)的代碼如下:

system.wii = ua.indexOf(\"Wii\") > -1;

system.ps = /playstation/i.test(ua);

對于 Wii,只要檢測字符串\"Wii\"就夠了,而其他代碼將發(fā)現(xiàn)這是一個 Opera 瀏覽器,并將正確的

版本號保存在 client.browser.opera 中。對于 Playstation,我們則使用正則表達式來以不區(qū)分大小

寫的方式測試用戶代理字符串。

9.3.3 完整的代碼

以下是完整的用戶代理字符串檢測腳本,包括檢測呈現(xiàn)引擎、平臺、Windows 操作系統(tǒng)、移動設(shè)備

和游戲系統(tǒng)。

var client = function(){

//呈現(xiàn)引擎

var engine = {

ie: 0,

gecko: 0,

webkit: 0,

khtml: 0,

opera: 0,

//完整的版本號

ver: null

};

//瀏覽器

var browser = {

//主要瀏覽器

ie: 0,

firefox: 0,

safari: 0,

konq: 0,

opera: 0,

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

第261頁

9.3 用戶代理檢測 243

1

2

3

4

5

13

6

7

8

9

10

11

12

chrome: 0,

//具體的版本號

ver: null

};

//平臺、設(shè)備和操作系統(tǒng)

var system = {

win: false,

mac: false,

x11: false,

//移動設(shè)備

iphone: false,

ipod: false,

ipad: false,

ios: false,

android: false,

nokiaN: false,

winMobile: false,

//游戲系統(tǒng)

wii: false,

ps: false

};

//檢測呈現(xiàn)引擎和瀏覽器

var ua = navigator.userAgent;

if (window.opera){

engine.ver = browser.ver = window.opera.version();

engine.opera = browser.opera = parseFloat(engine.ver);

} else if (/AppleWebKit\\/(\\S+)/.test(ua)){

engine.ver = RegExp[\"$1\"];

engine.webkit = parseFloat(engine.ver);

//確定是 Chrome 還是 Safari

if (/Chrome\\/(\\S+)/.test(ua)){

browser.ver = RegExp[\"$1\"];

browser.chrome = parseFloat(browser.ver);

} else if (/Version\\/(\\S+)/.test(ua)){

browser.ver = RegExp[\"$1\"];

browser.safari = parseFloat(browser.ver);

} else {

//近似地確定版本號

var safariVersion = 1;

if (engine.webkit < 100){

safariVersion = 1;

} else if (engine.webkit < 312){

safariVersion = 1.2;

} else if (engine.webkit < 412){

safariVersion = 1.3;

} else {

safariVersion = 2;

}

browser.safari = browser.ver = safariVersion;

}

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

第262頁

244 第 9 章 客戶端檢測

} else if (/KHTML\\/(\\S+)/.test(ua) || /Konqueror\\/([^;]+)/.test(ua)){

engine.ver = browser.ver = RegExp[\"$1\"];

engine.khtml = browser.konq = parseFloat(engine.ver);

} else if (/rv:([^\\)]+)\\) Gecko\\/\\d{8}/.test(ua)){

engine.ver = RegExp[\"$1\"];

engine.gecko = parseFloat(engine.ver);

//確定是不是 Firefox

if (/Firefox\\/(\\S+)/.test(ua)){

browser.ver = RegExp[\"$1\"];

browser.firefox = parseFloat(browser.ver);

}

} else if (/MSIE ([^;]+)/.test(ua)){

engine.ver = browser.ver = RegExp[\"$1\"];

engine.ie = browser.ie = parseFloat(engine.ver);

}

//檢測瀏覽器

browser.ie = engine.ie;

browser.opera = engine.opera;

//檢測平臺

var p = navigator.platform;

system.win = p.indexOf(\"Win\") == 0;

system.mac = p.indexOf(\"Mac\") == 0;

system.x11 = (p == \"X11\") || (p.indexOf(\"Linux\") == 0);

//檢測 Windows 操作系統(tǒng)

if (system.win){

if (/Win(?:dows )?([^do]{2})\\s?(\\d+\\.\\d+)?/.test(ua)){

if (RegExp[\"$1\"] == \"NT\"){

switch(RegExp[\"$2\"]){

case \"5.0\":

system.win = \"2000\";

break;

case \"5.1\":

system.win = \"XP\";

break;

case \"6.0\":

system.win = \"Vista\";

break;

case \"6.1\":

system.win = \"7\";

break;

default:

system.win = \"NT\";

break;

}

} else if (RegExp[\"$1\"] == \"9x\"){

system.win = \"ME\";

} else {

system.win = RegExp[\"$1\"];

}

}

}

//移動設(shè)備

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

第263頁

9.3 用戶代理檢測 245

1

2

3

4

5

13

6

7

8

9

10

11

12

system.iphone = ua.indexOf(\"iPhone\") > -1;

system.ipod = ua.indexOf(\"iPod\") > -1;

system.ipad = ua.indexOf(\"iPad\") > -1;

system.nokiaN = ua.indexOf(\"NokiaN\") > -1;

//windows mobile

if (system.win == \"CE\"){

system.winMobile = system.win;

} else if (system.win == \"Ph\"){

if(/Windows Phone OS (\\d+.\\d+)/.test(ua)){;

system.win = \"Phone\";

system.winMobile = parseFloat(RegExp[\"$1\"]);

}

}

//檢測 iOS 版本

if (system.mac && ua.indexOf(\"Mobile\") > -1){

if (/CPU (?:iPhone )?OS (\\d+_\\d+)/.test(ua)){

system.ios = parseFloat(RegExp.$1.replace(\"_\", \".\"));

} else {

system.ios = 2; //不能真正檢測出來,所以只能猜測

}

}

//檢測 Android 版本

if (/Android (\\d+\\.\\d+)/.test(ua)){

system.android = parseFloat(RegExp.$1);

}

//游戲系統(tǒng)

system.wii = ua.indexOf(\"Wii\") > -1;

system.ps = /playstation/i.test(ua);

//返回這些對象

return {

engine: engine,

browser: browser,

system: system

};

}();

client.js

9.3.4 使用方法

我們在前面已經(jīng)強調(diào)過了,用戶代理檢測是客戶端檢測的最后一個選擇。只要可能,都應該優(yōu)先采

用能力檢測和怪癖檢測。用戶代理檢測一般適用于下列情形。

? 不能直接準確地使用能力檢測或怪癖檢測。例如,某些瀏覽器實現(xiàn)了為將來功能預留的存根

(stub)函數(shù)。在這種情況下,僅測試相應的函數(shù)是否存在還得不到足夠的信息。

? 同一款瀏覽器在不同平臺下具備不同的能力。這時候,可能就有必要確定瀏覽器位于哪個平

臺下。

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

第264頁

246 第 9 章 客戶端檢測

? 為了跟蹤分析等目的需要知道確切的瀏覽器。

9.4 小結(jié)

客戶端檢測是 JavaScript 開發(fā)中最具爭議的一個話題。由于瀏覽器間存在差別,通常需要根據(jù)不同

瀏覽器的能力分別編寫不同的代碼。有不少客戶端檢測方法,但下列是最經(jīng)常使用的。

? 能力檢測:在編寫代碼之前先檢測特定瀏覽器的能力。例如,腳本在調(diào)用某個函數(shù)之前,可能

要先檢測該函數(shù)是否存在。這種檢測方法將開發(fā)人員從考慮具體的瀏覽器類型和版本中解放出

來,讓他們把注意力集中到相應的能力是否存在上。能力檢測無法精確地檢測特定的瀏覽器和

版本。

? 怪癖檢測:怪癖實際上是瀏覽器實現(xiàn)中存在的 bug,例如早期的 WebKit 中就存在一個怪癖,即

它會在 for-in 循環(huán)中返回被隱藏的屬性。怪癖檢測通常涉及到運行一小段代碼,然后確定瀏

覽器是否存在某個怪癖。由于怪癖檢測與能力檢測相比效率更低,因此應該只在某個怪癖會干

擾腳本運行的情況下使用。怪癖檢測無法精確地檢測特定的瀏覽器和版本。

? 用戶代理檢測:通過檢測用戶代理字符串來識別瀏覽器。用戶代理字符串中包含大量與瀏覽器

有關(guān)的信息,包括瀏覽器、平臺、操作系統(tǒng)及瀏覽器版本。用戶代理字符串有過一段相當長的

發(fā)展歷史,在此期間,瀏覽器提供商試圖通過在用戶代理字符串中添加一些欺騙性信息,欺騙

網(wǎng)站相信自己的瀏覽器是另外一種瀏覽器。用戶代理檢測需要特殊的技巧,特別是要注意 Opera

會隱瞞其用戶代理字符串的情況。即便如此,通過用戶代理字符串仍然能夠檢測出瀏覽器所用

的呈現(xiàn)引擎以及所在的平臺,包括移動設(shè)備和游戲系統(tǒng)。

在決定使用哪種客戶端檢測方法時,一般應優(yōu)先考慮使用能力檢測。怪癖檢測是確定應該如何處理

代碼的第二選擇。而用戶代理檢測則是客戶端檢測的最后一種方案,因為這種方法對用戶代理字符串具

有很強的依賴性。

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

第265頁

10.1 節(jié)點層次 247

1

2

3

4

5

13

6

7

8

9

10

11

12

DOM

本章內(nèi)容

? 理解包含不同層次節(jié)點的 DOM

? 使用不同的節(jié)點類型

? 克服瀏覽器兼容性問題及各種陷阱

OM(文檔對象模型)是針對 HTML 和 XML 文檔的一個 API(應用程序編程接口)。DOM 描

繪了一個層次化的節(jié)點樹,允許開發(fā)人員添加、移除和修改頁面的某一部分。DOM 脫胎于

Netscape 及微軟公司創(chuàng)始的 DHTML(動態(tài) HTML),但現(xiàn)在它已經(jīng)成為表現(xiàn)和操作頁面標記的真正的跨

平臺、語言中立的方式。

1998 年 10 月 DOM1級規(guī)范成為 W3C 的推薦標準,為基本的文檔結(jié)構(gòu)及查詢提供了接口。本章主

要討論與瀏覽器中的 HTML 頁面相關(guān)的 DOM1 級的特性和應用,以及 JavaScript 對 DOM1 級的實現(xiàn)。

IE、Firefox、Safari、Chrome 和 Opera 都非常完善地實現(xiàn)了 DOM。

注意,IE 中的所有 DOM 對象都是以 COM 對象的形式實現(xiàn)的。這意味著 IE 中的

DOM 對象與原生 JavaScript 對象的行為或活動特點并不一致。本章將較多地談及這些

差異。

10.1 節(jié)點層次

DOM 可以將任何 HTML 或 XML 文檔描繪成一個由多層節(jié)點構(gòu)成的結(jié)構(gòu)。節(jié)點分為幾種不同的類

型,每種類型分別表示文檔中不同的信息及(或)標記。每個節(jié)點都擁有各自的特點、數(shù)據(jù)和方法,另

外也與其他節(jié)點存在某種關(guān)系。節(jié)點之間的關(guān)系構(gòu)成了層次,而所有頁面標記則表現(xiàn)為一個以特定節(jié)點

為根節(jié)點的樹形結(jié)構(gòu)。以下面的 HTML 為例:

<html>

<head>

<title>Sample Page</title>

</head>

<body>

<p>Hello World!</p>

</body>

</html>

可以將這個簡單的 HTML 文檔表示為一個層次結(jié)構(gòu),如圖 10-1 所示。

文檔節(jié)點是每個文檔的根節(jié)點。在這個例子中,文檔節(jié)點只有一個子節(jié)點,即<html>元素,我們

D

第 10 章

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

第266頁

248 第 10 章 DOM

稱之為文檔元素。文檔元素是文檔的最外層元素,文檔中的其他所有元素都包含在文檔元素中。每個文

檔只能有一個文檔元素。在 HTML 頁面中,文檔元素始終都是<html>元素。在 XML 中,沒有預定義

的元素,因此任何元素都可能成為文檔元素。

每一段標記都可以通過樹中的一個節(jié)點來表示:HTML 元素通過元素節(jié)點表示,特性(attribute)

通過特性節(jié)點表示,文檔類型通過文檔類型節(jié)點表示,而注釋則通過注釋節(jié)點表示。總共有 12 種節(jié)點

類型,這些類型都繼承自一個基類型。

圖 10-1

10.1.1 Node類型

DOM1 級定義了一個 Node 接口,該接口將由 DOM 中的所有節(jié)點類型實現(xiàn)。這個 Node 接口在

JavaScript 中是作為 Node 類型實現(xiàn)的;除了 IE 之外,在其他所有瀏覽器中都可以訪問到這個類型。

JavaScript 中的所有節(jié)點類型都繼承自 Node 類型,因此所有節(jié)點類型都共享著相同的基本屬性和方法。

每個節(jié)點都有一個 nodeType 屬性,用于表明節(jié)點的類型。節(jié)點類型由在 Node 類型中定義的下列

12 個數(shù)值常量來表示,任何節(jié)點類型必居其一:

? Node.ELEMENT_NODE(1);

? Node.ATTRIBUTE_NODE(2);

? Node.TEXT_NODE(3);

? Node.CDATA_SECTION_NODE(4);

? Node.ENTITY_REFERENCE_NODE(5);

? Node.ENTITY_NODE(6);

? Node.PROCESSING_INSTRUCTION_NODE(7);

? Node.COMMENT_NODE(8);

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

第267頁

10.1 節(jié)點層次 249

1

2

3

4

5

13

6

7

8

9

10

11

12

? Node.DOCUMENT_NODE(9);

? Node.DOCUMENT_TYPE_NODE(10);

? Node.DOCUMENT_FRAGMENT_NODE(11);

? Node.NOTATION_NODE(12)。

通過比較上面這些常量,可以很容易地確定節(jié)點的類型,例如:

if (someNode.nodeType == Node.ELEMENT_NODE){ //在 IE 中無效

alert(\"Node is an element.\");

}

這個例子比較了 someNode.nodeType 與 Node.ELEMENT_NODE 常量。如果二者相等,則意味著

someNode 確實是一個元素。然而,由于 IE 沒有公開 Node 類型的構(gòu)造函數(shù),因此上面的代碼在 IE 中

會導致錯誤。為了確??鐬g覽器兼容,最好還是將 nodeType 屬性與數(shù)字值進行比較,如下所示:

if (someNode.nodeType == 1){ //適用于所有瀏覽器

alert(\"Node is an element.\");

}

并不是所有節(jié)點類型都受到 Web 瀏覽器的支持。開發(fā)人員最常用的就是元素和文本節(jié)點。本章后

面將詳細討論每個節(jié)點類型的受支持情況及使用方法。

1. nodeName 和 nodeValue 屬性

要了解節(jié)點的具體信息,可以使用 nodeName 和 nodeValue 這兩個屬性。這兩個屬性的值完全取

決于節(jié)點的類型。在使用這兩個值以前,最好是像下面這樣先檢測一下節(jié)點的類型。

if (someNode.nodeType == 1){

value = someNode.nodeName; //nodeName 的值是元素的標簽名

}

在這個例子中,首先檢查節(jié)點類型,看它是不是一個元素。如果是,則取得并保存 nodeName 的值。

對于元素節(jié)點,nodeName 中保存的始終都是元素的標簽名,而 nodeValue 的值則始終為 null。

2. 節(jié)點關(guān)系

文檔中所有的節(jié)點之間都存在這樣或那樣的關(guān)系。節(jié)點間的各種關(guān)系可以用傳統(tǒng)的家族關(guān)系來描

述,相當于把文檔樹比喻成家譜。在 HTML 中,可以將<body>元素看成是<html>元素的子元素;相應

地,也就可以將<html>元素看成是<body>元素的父元素。而<head>元素,則可以看成是<body>元素

的同胞元素,因為它們都是同一個父元素<html>的直接子元素。

每個節(jié)點都有一個 childNodes 屬性,其中保存著一個 NodeList 對象。NodeList 是一種類數(shù)組

對象,用于保存一組有序的節(jié)點,可以通過位置來訪問這些節(jié)點。請注意,雖然可以通過方括號語法來

訪問 NodeList 的值,而且這個對象也有 length 屬性,但它并不是 Array 的實例。NodeList 對象的

獨特之處在于,它實際上是基于 DOM 結(jié)構(gòu)動態(tài)執(zhí)行查詢的結(jié)果,因此 DOM 結(jié)構(gòu)的變化能夠自動反映

在 NodeList 對象中。我們常說,NodeList 是有生命、有呼吸的對象,而不是在我們第一次訪問它們

的某個瞬間拍攝下來的一張快照。

下面的例子展示了如何訪問保存在 NodeList 中的節(jié)點——可以通過方括號,也可以使用 item()

方法。

var firstChild = someNode.childNodes[0];

var secondChild = someNode.childNodes.item(1);

var count = someNode.childNodes.length;

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

第268頁

250 第 10 章 DOM

無論使用方括號還是使用 item()方法都沒有問題,但使用方括號語法看起來與訪問數(shù)組相似,因

此頗受一些開發(fā)人員的青睞。另外,要注意 length 屬性表示的是訪問 NodeList 的那一刻,其中包含

的節(jié)點數(shù)量。我們在本書前面介紹過,對 arguments 對象使用 Array.prototype.slice()方法可以

將其轉(zhuǎn)換為數(shù)組。而采用同樣的方法,也可以將 NodeList 對象轉(zhuǎn)換為數(shù)組。來看下面的例子:

//在 IE8 及之前版本中無效

var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);

除 IE8 及更早版本之外,這行代碼能在任何瀏覽器中運行。由于 IE8 及更早版本將 NodeList

實現(xiàn)為一個 COM 對象,而我們不能像使用 JScript 對象那樣使用這種對象,因此上面的代碼會導致

錯誤。要想在 IE 中將 NodeList 轉(zhuǎn)換為數(shù)組,必須手動枚舉所有成員。下列代碼在所有瀏覽器中都

可以運行:

function convertToArray(nodes){

var array = null;

try {

array = Array.prototype.slice.call(nodes, 0); //針對非 IE 瀏覽器

} catch (ex) {

array = new Array();

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

array.push(nodes[i]);

}

}

return array;

}

這個 convertToArray()函數(shù)首先嘗試了創(chuàng)建數(shù)組的最簡單方式。如果導致了錯誤(說明是在

IE8 及更早版本中),則通過 try-catch 塊來捕獲錯誤,然后手動創(chuàng)建數(shù)組。這是另一種檢測怪癖的

形式。

每個節(jié)點都有一個 parentNode 屬性,該屬性指向文檔樹中的父節(jié)點。包含在 childNodes 列表中

的所有節(jié)點都具有相同的父節(jié)點,因此它們的 parentNode 屬性都指向同一個節(jié)點。此外,包含在

childNodes 列表中的每個節(jié)點相互之間都是同胞節(jié)點。通過使用列表中每個節(jié)點的 previousSibling

和 nextSibling 屬性,可以訪問同一列表中的其他節(jié)點。列表中第一個節(jié)點的 previousSibling 屬性

值為 null,而列表中最后一個節(jié)點的 nextSibling 屬性的值同樣也為 null,如下面的例子所示:

if (someNode.nextSibling === null){

alert(\"Last node in the parent’s childNodes list.\");

} else if (someNode.previousSibling === null){

alert(\"First node in the parent’s childNodes list.\");

}

當然,如果列表中只有一個節(jié)點,那么該節(jié)點的 nextSibling 和 previousSibling 都為 null。

父節(jié)點與其第一個和最后一個子節(jié)點之間也存在特殊關(guān)系。父節(jié)點的 firstChild 和 lastChild

屬性分別指向其 childNodes 列表中的第一個和最后一個節(jié)點。其中,someNode.firstChild 的值

始終等于 someNode.childNodes[0] , 而 someNode.lastChild 的值始終等于 someNode.

childNodes [someNode.childNodes.length-1]。在只有一個子節(jié)點的情況下,firstChild 和

lastChild 指向同一個節(jié)點。如果沒有子節(jié)點,那么 firstChild 和 lastChild 的值均為 null。明

確這些關(guān)系能夠?qū)ξ覀儾檎液驮L問文檔結(jié)構(gòu)中的節(jié)點提供極大的便利。圖 10-2 形象地展示了上述關(guān)系。

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

第269頁

10.1 節(jié)點層次 251

1

2

3

4

5

13

6

7

8

9

10

11

12

圖 10-2

在反映這些關(guān)系的所有屬性當中,childNodes 屬性與其他屬性相比更方便一些,因為只須使用簡

單的關(guān)系指針,就可以通過它訪問文檔樹中的任何節(jié)點。另外,hasChildNodes()也是一個非常有用

的方法,這個方法在節(jié)點包含一或多個子節(jié)點的情況下返回 true;應該說,這是比查詢 childNodes

列表的 length 屬性更簡單的方法。

所有節(jié)點都有的最后一個屬性是 ownerDocument,該屬性指向表示整個文檔的文檔節(jié)點。這種關(guān)

系表示的是任何節(jié)點都屬于它所在的文檔,任何節(jié)點都不能同時存在于兩個或更多個文檔中。通過這個

屬性,我們可以不必在節(jié)點層次中通過層層回溯到達頂端,而是可以直接訪問文檔節(jié)點。

雖然所有節(jié)點類型都繼承自 Node,但并不是每種節(jié)點都有子節(jié)點。本章后面將

會討論不同節(jié)點類型之間的差異。

3. 操作節(jié)點

因為關(guān)系指針都是只讀的,所以 DOM 提供了一些操作節(jié)點的方法。其中,最常用的方法是

appendChild(),用于向 childNodes 列表的末尾添加一個節(jié)點。添加節(jié)點后,childNodes 的新增

節(jié)點、父節(jié)點及以前的最后一個子節(jié)點的關(guān)系指針都會相應地得到更新。更新完成后,appendChild()

返回新增的節(jié)點。來看下面的例子:

var returnedNode = someNode.appendChild(newNode);

alert(returnedNode == newNode); //true

alert(someNode.lastChild == newNode); //true

如果傳入到 appendChild()中的節(jié)點已經(jīng)是文檔的一部分了,那結(jié)果就是將該節(jié)點從原來的位置

轉(zhuǎn)移到新位置。即使可以將 DOM 樹看成是由一系列指針連接起來的,但任何 DOM 節(jié)點也不能同時出

現(xiàn)在文檔中的多個位置上。因此,如果在調(diào)用 appendChild()時傳入了父節(jié)點的第一個子節(jié)點,那么

該節(jié)點就會成為父節(jié)點的最后一個子節(jié)點,如下面的例子所示。

//someNode 有多個子節(jié)點

var returnedNode = someNode.appendChild(someNode.firstChild);

alert(returnedNode == someNode.firstChild); //false

alert(returnedNode == someNode.lastChild); //true

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

第270頁

252 第 10 章 DOM

如果需要把節(jié)點放在 childNodes 列表中某個特定的位置上,而不是放在末尾,那么可以使用

insertBefore()方法。這個方法接受兩個參數(shù):要插入的節(jié)點和作為參照的節(jié)點。插入節(jié)點后,被插

入的節(jié)點會變成參照節(jié)點的前一個同胞節(jié)點(previousSibling),同時被方法返回。如果參照節(jié)點是

null,則 insertBefore()與 appendChild()執(zhí)行相同的操作,如下面的例子所示。

//插入后成為最后一個子節(jié)點

returnedNode = someNode.insertBefore(newNode, null);

alert(newNode == someNode.lastChild); //true

//插入后成為第一個子節(jié)點

var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);

alert(returnedNode == newNode); //true

alert(newNode == someNode.firstChild); //true

//插入到最后一個子節(jié)點前面

returnedNode = someNode.insertBefore(newNode, someNode.lastChild);

alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true

前面介紹的 appendChild()和 insertBefore()方法都只插入節(jié)點,不會移除節(jié)點。而下面要介

紹的 replaceChild()方法接受的兩個參數(shù)是:要插入的節(jié)點和要替換的節(jié)點。要替換的節(jié)點將由這個

方法返回并從文檔樹中被移除,同時由要插入的節(jié)點占據(jù)其位置。來看下面的例子。

//替換第一個子節(jié)點

var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);

//替換最后一個子節(jié)點

returnedNode = someNode.replaceChild(newNode, someNode.lastChild);

在使用 replaceChild()插入一個節(jié)點時,該節(jié)點的所有關(guān)系指針都會從被它替換的節(jié)點復制過

來。盡管從技術(shù)上講,被替換的節(jié)點仍然還在文檔中,但它在文檔中已經(jīng)沒有了自己的位置。

如果只想移除而非替換節(jié)點,可以使用 removeChild()方法。這個方法接受一個參數(shù),即要移除

的節(jié)點。被移除的節(jié)點將成為方法的返回值,如下面的例子所示。

//移除第一個子節(jié)點

var formerFirstChild = someNode.removeChild(someNode.firstChild);

//移除最后一個子節(jié)點

var formerLastChild = someNode.removeChild(someNode.lastChild);

與使用 replaceChild()方法一樣,通過 removeChild()移除的節(jié)點仍然為文檔所有,只不過在

文檔中已經(jīng)沒有了自己的位置。

前面介紹的四個方法操作的都是某個節(jié)點的子節(jié)點,也就是說,要使用這幾個方法必須先取得父節(jié)

點(使用 parentNode 屬性)。另外,并不是所有類型的節(jié)點都有子節(jié)點,如果在不支持子節(jié)點的節(jié)點

上調(diào)用了這些方法,將會導致錯誤發(fā)生。

4. 其他方法

有兩個方法是所有類型的節(jié)點都有的。第一個就是 cloneNode(),用于創(chuàng)建調(diào)用這個方法的節(jié)點

的一個完全相同的副本。cloneNode()方法接受一個布爾值參數(shù),表示是否執(zhí)行深復制。在參數(shù)為 true

的情況下,執(zhí)行深復制,也就是復制節(jié)點及其整個子節(jié)點樹;在參數(shù)為 false 的情況下,執(zhí)行淺復制,

即只復制節(jié)點本身。復制后返回的節(jié)點副本屬于文檔所有,但并沒有為它指定父節(jié)點。因此,這個節(jié)點

副本就成為了一個“孤兒”,除非通過 appendChild()、insertBefore()或 replaceChild()將它

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

第271頁

10.1 節(jié)點層次 253

1

2

3

4

5

13

6

7

8

9

10

11

12

添加到文檔中。例如,假設(shè)有下面的 HTML 代碼。

<ul>

<li>item 1</li>

<li>item 2</li>

<li>item 3</li>

</ul>

如果我們已經(jīng)將<ul>元素的引用保存在了變量 myList 中,那么通常下列代碼就可以看出使用

cloneNode()方法的兩種模式。

var deepList = myList.cloneNode(true);

alert(deepList.childNodes.length); //3(IE < 9)或 7(其他瀏覽器)

var shallowList = myList.cloneNode(false);

alert(shallowList.childNodes.length); //0

在這個例子中,deepList 中保存著一個對 myList 執(zhí)行深復制得到的副本。因此,deepList 中

包含 3 個列表項,每個列表項中都包含文本。而變量 shallowList 中保存著對 myList 執(zhí)行淺復制得

到的副本,因此它不包含子節(jié)點。deepList.childNodes.length 中的差異主要是因為 IE8 及更早版

本與其他瀏覽器處理空白字符的方式不一樣。IE9 之前的版本不會為空白符創(chuàng)建節(jié)點。

cloneNode()方法不會復制添加到 DOM 節(jié)點中的 JavaScript 屬性,例如事件處

理程序等。這個方法只復制特性、(在明確指定的情況下也復制)子節(jié)點,其他一切

都不會復制。IE 在此存在一個 bug,即它會復制事件處理程序,所以我們建議在復制

之前最好先移除事件處理程序。

我們要介紹的最后一個方法是 normalize(),這個方法唯一的作用就是處理文檔樹中的文本節(jié)點。

由于解析器的實現(xiàn)或 DOM 操作等原因,可能會出現(xiàn)文本節(jié)點不包含文本,或者接連出現(xiàn)兩個文本節(jié)點

的情況。當在某個節(jié)點上調(diào)用這個方法時,就會在該節(jié)點的后代節(jié)點中查找上述兩種情況。如果找到了

空文本節(jié)點,則刪除它;如果找到相鄰的文本節(jié)點,則將它們合并為一個文本節(jié)點。本章后面還將進一

步討論這個方法。

10.1.2 Document類型

JavaScript 通過 Document 類型表示文檔。在瀏覽器中,document 對象是 HTMLDocument(繼承

自 Document 類型)的一個實例,表示整個 HTML 頁面。而且,document 對象是 window 對象的一個

屬性,因此可以將其作為全局對象來訪問。Document 節(jié)點具有下列特征:

? nodeType 的值為 9;

? nodeName 的值為\"#document\";

? nodeValue 的值為 null;

? parentNode 的值為 null;

? ownerDocument 的值為 null;

? 其子節(jié)點可能是一個 DocumentType(最多一個)、Element(最多一個)、ProcessingInstruction

或 Comment。

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

第272頁

254 第 10 章 DOM

Document 類型可以表示 HTML 頁面或者其他基于 XML 的文檔。不過,最常見的應用還是作為

HTMLDocument 實例的 document 對象。通過這個文檔對象,不僅可以取得與頁面有關(guān)的信息,而且還

能操作頁面的外觀及其底層結(jié)構(gòu)。

在 Firefox、Safari、Chrome 和 Opera 中,可以通過腳本訪問 Document 類型的構(gòu)

造函數(shù)和原型。但在所有瀏覽器中都可以訪問 HTMLDocument 類型的構(gòu)造函數(shù)和原型,

包括 IE8及后續(xù)版本。

1. 文檔的子節(jié)點

雖然 DOM 標準規(guī)定 Document 節(jié)點的子節(jié)點可以是 DocumentType、Element、ProcessingInstruction 或 Comment,但還有兩個內(nèi)置的訪問其子節(jié)點的快捷方式。第一個就是 documentElement

屬性,該屬性始終指向 HTML 頁面中的<html>元素。另一個就是通過 childNodes 列表訪問文檔元素,

但通過 documentElement 屬性則能更快捷、更直接地訪問該元素。以下面這個簡單的頁面為例。

<html>

<body>

</body>

</html>

這個頁面在經(jīng)過瀏覽器解析后,其文檔中只包含一個子節(jié)點,即<html>元素??梢酝ㄟ^

documentElement 或 childNodes 列表來訪問這個元素,如下所示。

var html = document.documentElement; //取得對<html>的引用

alert(html === document.childNodes[0]); //true

alert(html === document.firstChild); //true

這個例子說明,documentElement、firstChild 和 childNodes[0]的值相同,都指向<html>

元素。

作為 HTMLDocument 的實例,document 對象還有一個 body 屬性,直接指向<body>元素。因為開

發(fā)人員經(jīng)常要使用這個元素,所以 document.body 在 JavaScript代碼中出現(xiàn)的頻率非常高,其用法如下。

var body = document.body; //取得對<body>的引用

所有瀏覽器都支持 document.documentElement 和 document.body 屬性。

Document 另一個可能的子節(jié)點是 DocumentType。通常將<!DOCTYPE>標簽看成一個與文檔其他

部分不同的實體,可以通過 doctype 屬性(在瀏覽器中是 document.doctype)來訪問它的信息。

var doctype = document.doctype; //取得對<!DOCTYPE>的引用

瀏覽器對 document.doctype 的支持差別很大,可以給出如下總結(jié)。

? IE8 及之前版本:如果存在文檔類型聲明,會將其錯誤地解釋為一個注釋并把它當作 Comment

節(jié)點;而 document.doctype 的值始終為 null。

? IE9+及 Firefox:如果存在文檔類型聲明,則將其作為文檔的第一個子節(jié)點;document.doctype

是一個 DocumentType 節(jié)點,也可以通過 document.firstChild 或 document.childNodes[0]

訪問同一個節(jié)點。

? Safari、Chrome 和 Opera:如果存在文檔類型聲明,則將其解析,但不作為文檔的子節(jié)點。document.doctype 是一個 DocumentType 節(jié)點,但該節(jié)點不會出現(xiàn)在 document.childNodes 中。

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

第273頁

10.1 節(jié)點層次 255

1

2

3

4

5

13

6

7

8

9

10

11

12

由于瀏覽器對 document.doctype 的支持不一致,因此這個屬性的用處很有限。

從技術(shù)上說,出現(xiàn)在<html>元素外部的注釋應該算是文檔的子節(jié)點。然而,不同的瀏覽器在是否

解析這些注釋以及能否正確處理它們等方面,也存在很大差異。以下面簡單的 HTML 頁面為例。

<!--第一條注釋 -->

<html>

<body>

</body>

</html>

<!--第二條注釋 -->

看起來這個頁面應該有 3 個子節(jié)點:注釋、<html>元素、注釋。從邏輯上講,我們會認為

document.childNodes 中應該包含與這 3 個節(jié)點對應的 3 項。但是,現(xiàn)實中的瀏覽器在處理位于

<html>外部的注釋方面存在如下差異。

? IE8 及之前版本、Safari 3.1 及更高版本、Opera 和 Chrome 只為第一條注釋創(chuàng)建節(jié)點,不為第二

條注釋創(chuàng)建節(jié)點。結(jié)果,第一條注釋就會成為 document.childNodes 中的第一個子節(jié)點。

? IE9 及更高版本會將第一條注釋創(chuàng)建為 document.childNodes 中的一個注釋節(jié)點,也會將第

二條注釋創(chuàng)建為 document.childNodes 中的注釋子節(jié)點。

? Firefox 以及 Safari 3.1 之前的版本會完全忽略這兩條注釋。

同樣,瀏覽器間的這種不一致性也導致了位于<html>元素外部的注釋沒有什么用處。

多數(shù)情況下,我們都用不著在 document 對象上調(diào)用 appendChild()、removeChild()和

replaceChild()方法,因為文檔類型(如果存在的話)是只讀的,而且它只能有一個元素子節(jié)點(該

節(jié)點通常早就已經(jīng)存在了)。

2. 文檔信息

作為 HTMLDocument 的一個實例,document 對象還有一些標準的 Document 對象所沒有的屬性。

這些屬性提供了 document 對象所表現(xiàn)的網(wǎng)頁的一些信息。其中第一個屬性就是 title,包含著

<title>元素中的文本——顯示在瀏覽器窗口的標題欄或標簽頁上。通過這個屬性可以取得當前頁面的

標題,也可以修改當前頁面的標題并反映在瀏覽器的標題欄中。修改 title 屬性的值不會改變<title>

元素。來看下面的例子。

//取得文檔標題

var originalTitle = document.title;

//設(shè)置文檔標題

document.title = \"New page title\";

接下來要介紹的 3 個屬性都與對網(wǎng)頁的請求有關(guān),它們是 URL、domain 和 referrer。URL 屬性

中包含頁面完整的 URL(即地址欄中顯示的 URL),domain 屬性中只包含頁面的域名,而 referrer

屬性中則保存著鏈接到當前頁面的那個頁面的 URL。在沒有來源頁面的情況下,referrer 屬性中可能

會包含空字符串。所有這些信息都存在于請求的 HTTP 頭部,只不過是通過這些屬性讓我們能夠在

JavaScrip 中訪問它們而已,如下面的例子所示。

//取得完整的 URL

var url = document.URL;

//取得域名

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

第274頁

256 第 10 章 DOM

var domain = document.domain;

//取得來源頁面的 URL

var referrer = document.referrer;

URL 與 domain 屬性是相互關(guān)聯(lián)的。例如,如果 document.URL 等于 http://www.wrox.com/WileyCDA/,

那么 document.domain 就等于 www.wrox.com。

在這 3 個屬性中,只有 domain 是可以設(shè)置的。但由于安全方面的限制,也并非可以給 domain 設(shè)

置任何值。如果 URL 中包含一個子域名,例如 p2p.wrox.com,那么就只能將 domain 設(shè)置為\"wrox.com\"

(URL 中包含\"www\",如 www.wrox.com 時,也是如此)。不能將這個屬性設(shè)置為 URL 中不包含的域,

如下面的例子所示。

//假設(shè)頁面來自 p2p.wrox.com 域

document.domain = \"wrox.com\"; // 成功

document.domain = \"nczonline.net\"; // 出錯!

當頁面中包含來自其他子域的框架或內(nèi)嵌框架時,能夠設(shè)置 document.domain 就非常方便了。由

于跨域安全限制,來自不同子域的頁面無法通過 JavaScript 通信。而通過將每個頁面的

document.domain 設(shè)置為相同的值,這些頁面就可以互相訪問對方包含的 JavaScript 對象了。例如,

假設(shè)有一個頁面加載自 www.wrox.com,其中包含一個內(nèi)嵌框架,框架內(nèi)的頁面加載自 p2p.wrox.com。

由于 document.domain 字符串不一樣,內(nèi)外兩個頁面之間無法相互訪問對方的 JavaScript 對象。但如

果將這兩個頁面的 document.domain 值都設(shè)置為\"wrox.com\",它們之間就可以通信了。

瀏覽器對 domain 屬性還有一個限制,即如果域名一開始是“松散的”(loose),那么不能將它再設(shè)

置為“緊繃的”(tight)。換句話說,在將 document.domain 設(shè)置為\"wrox.com\"之后,就不能再將其

設(shè)置回\"p2p.wrox.com\",否則將會導致錯誤,如下面的例子所示。

//假設(shè)頁面來自于 p2p.wrox.com 域

document.domain = \"wrox.com\"; //松散的(成功)

document.domain = \"p2p.wrox.com\"; //緊繃的(出錯?。?/p>

所有瀏覽器中都存在這個限制,但 IE8 是實現(xiàn)這一限制的最早的 IE 版本。

3. 查找元素

說到最常見的 DOM 應用,恐怕就要數(shù)取得特定的某個或某組元素的引用,然后再執(zhí)行一些操作了。

取得元素的操作可以使用 document 對象的幾個方法來完成。其中,Document 類型為此提供了兩個方

法:getElementById()和 getElementsByTagName()。

第一個方法,getElementById(),接收一個參數(shù):要取得的元素的 ID。如果找到相應的元素則

返回該元素,如果不存在帶有相應 ID 的元素,則返回 null。注意,這里的 ID 必須與頁面中元素的 id

特性(attribute)嚴格匹配,包括大小寫。以下面的元素為例。

<div id=\"myDiv\">Some text</div>

可以使用下面的代碼取得這個元素:

var div = document.getElementById(\"myDiv\"); //取得<div>元素的引用

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

第275頁

10.1 節(jié)點層次 257

1

2

3

4

5

13

6

7

8

9

10

11

12

但是,下面的代碼在除 IE7 及更早版本之外的所有瀏覽器中都將返回 null。

var div = document.getElementById(\"mydiv\"); //無效的 ID(在 IE7 及更早版本中可以)

IE8 及較低版本不區(qū)分 ID 的大小寫,因此\"myDiv\"和\"mydiv\"會被當作相同的元素 ID。

如果頁面中多個元素的 ID 值相同,getElementById()只返回文檔中第一次出現(xiàn)的元素。IE7 及較

低版本還為此方法添加了一個有意思的“怪癖”:name 特性與給定 ID 匹配的表單元素(<input>、

<textarea>、<button>及<select>)也會被該方法返回。如果有哪個表單元素的 name 特性等于指

定的 ID,而且該元素在文檔中位于帶有給定 ID 的元素前面,那么 IE 就會返回那個表單元素。來看下面

的例子。

<input type=\"text\" name=\"myElement\" value=\"Text field\">

<div id=\"myElement\">A div</div>

基于這段 HTML 代碼,在 IE7 中調(diào)用 document.getElementById(\"myElement \"),結(jié)果會返

回<input>元素;而在其他所有瀏覽器中,都會返回對<div>元素的引用。為了避免 IE 中存在的這個問

題,最好的辦法是不讓表單字段的 name 特性與其他元素的 ID 相同。

另一個常用于取得元素引用的方法是 getElementsByTagName()。這個方法接受一個參數(shù),即要

取得元素的標簽名,而返回的是包含零或多個元素的 NodeList。在 HTML 文檔中,這個方法會返回一

個 HTMLCollection 對象,作為一個“動態(tài)”集合,該對象與 NodeList 非常類似。例如,下列代碼

會取得頁面中所有的<img>元素,并返回一個 HTMLCollection。

var images = document.getElementsByTagName(\"img\");

這行代碼會將一個 HTMLCollection 對象保存在 images 變量中。與 NodeList 對象類似,可以

使用方括號語法或 item()方法來訪問 HTMLCollection 對象中的項。而這個對象中元素的數(shù)量則可以

通過其 length 屬性取得,如下面的例子所示。

alert(images.length); //輸出圖像的數(shù)量

alert(images[0].src); //輸出第一個圖像元素的 src 特性

alert(images.item(0).src); //輸出第一個圖像元素的 src 特性

HTMLCollection 對象還有一個方法,叫做 namedItem(),使用這個方法可以通過元素的 name

特性取得集合中的項。例如,假設(shè)上面提到的頁面中包含如下<img>元素:

<img src=\"myimage.gif\" name=\"myImage\">

那么就可以通過如下方式從 images 變量中取得這個<img>元素:

var myImage = images.namedItem(\"myImage\");

在提供按索引訪問項的基礎(chǔ)上,HTMLCollection 還支持按名稱訪問項,這就為我們?nèi)〉脤嶋H想要

的元素提供了便利。而且,對命名的項也可以使用方括號語法來訪問,如下所示:

var myImage = images[\"myImage\"];

對 HTMLCollection 而言,我們可以向方括號中傳入數(shù)值或字符串形式的索引值。在后臺,對數(shù)

值索引就會調(diào)用 item(),而對字符串索引就會調(diào)用 namedItem()。

要想取得文檔中的所有元素,可以向 getElementsByTagName()中傳入\"*\"。在 JavaScript 及 CSS

中,星號(*)通常表示“全部”。下面看一個例子。

var allElements = document.getElementsByTagName(\"*\");

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

第276頁

258 第 10 章 DOM

僅此一行代碼返回的 HTMLCollection 中,就包含了整個頁面中的所有元素——按照它們出現(xiàn)的

先后順序。換句話說,第一項是<html>元素,第二項是<head>元素,以此類推。由于 IE 將注釋(Comment)

實現(xiàn)為元素(Element),因此在 IE 中調(diào)用 getElementsByTagName(\"*\")將會返回所有注釋節(jié)點。

雖然標準規(guī)定標簽名需要區(qū)分大小寫,但為了最大限度地與既有 HTML 頁面兼

容,傳給 getElementsByTagName()的標簽名是不需要區(qū)分大小寫的。但對于 XML

頁面而言(包括 XHTML),getElementsByTagName()方法就會區(qū)分大小寫。

第三個方法,也是只有 HTMLDocument 類型才有的方法,是 getElementsByName()。顧名思義,

這個方法會返回帶有給定 name 特性的所有元素。最常使用 getElementsByName()方法的情況是取得

單選按鈕;為了確保發(fā)送給瀏覽器的值正確無誤,所有單選按鈕必須具有相同的 name 特性,如下面的

例子所示。

<fieldset>

<legend>Which color do you prefer?</legend>

<ul>

<li><input type=\"radio\" value=\"red\" name=\"color\" id=\"colorRed\">

<label for=\"colorRed\">Red</label></li>

<li><input type=\"radio\" value=\"green\" name=\"color\" id=\"colorGreen\">

<label for=\"colorGreen\">Green</label></li>

<li><input type=\"radio\" value=\"blue\" name=\"color\" id=\"colorBlue\">

<label for=\"colorBlue\">Blue</label></li>

</ul>

</fieldset>

如這個例子所示,其中所有單選按鈕的 name 特性值都是\"color\",但它們的 ID 可以不同。ID 的

作用在于將<label>元素應用到每個單選按鈕,而 name 特性則用以確保三個值中只有一個被發(fā)送給瀏

覽器。這樣,我們就可以使用如下代碼取得所有單選按鈕:

var radios = document.getElementsByName(\"color\");

與 getElementsByTagName()類似,getElementsByName()方法也會返回一個 HTMLCollectioin。

但是,對于這里的單選按鈕來說,namedItem()方法則只會取得第一項(因為每一項的 name 特性都相同)。

4. 特殊集合

除了屬性和方法,document 對象還有一些特殊的集合。這些集合都是 HTMLCollection 對象,

為訪問文檔常用的部分提供了快捷方式,包括:

? document.anchors,包含文檔中所有帶 name 特性的<a>元素;

? document.applets,包含文檔中所有的<applet>元素,因為不再推薦使用<applet>元素,

所以這個集合已經(jīng)不建議使用了;

? document.forms,包含文檔中所有的<form>元素,與 document.getElementsByTagName(\"form\")

得到的結(jié)果相同;

? document.images,包含文檔中所有的<img>元素,與 document.getElementsByTagName

(\"img\")得到的結(jié)果相同;

? document.links,包含文檔中所有帶 href 特性的<a>元素。

這個特殊集合始終都可以通過 HTMLDocument 對象訪問到,而且,與 HTMLCollection 對象類似,

集合中的項也會隨著當前文檔內(nèi)容的更新而更新。

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

第277頁

10.1 節(jié)點層次 259

1

2

3

4

5

13

6

7

8

9

10

11

12

5. DOM 一致性檢測

由于 DOM 分為多個級別,也包含多個部分,因此檢測瀏覽器實現(xiàn)了 DOM 的哪些部分就十分必要

了。document.implementation 屬性就是為此提供相應信息和功能的對象,與瀏覽器對 DOM 的實現(xiàn)

直接對應。DOM1 級只為 document.implementation 規(guī)定了一個方法,即 hasFeature()。這個方

法接受兩個參數(shù):要檢測的 DOM 功能的名稱及版本號。如果瀏覽器支持給定名稱和版本的功能,則該

方法返回 true,如下面的例子所示:

var hasXmlDom = document.implementation.hasFeature(\"XML\", \"1.0\");

下表列出了可以檢測的不同的值及版本號。

功 能 版 本 號 說 明

Core 1.0、2.0、3.0 基本的DOM,用于描述表現(xiàn)文檔的節(jié)點樹

XML 1.0、2.0、3.0 Core的XML擴展,添加了對CDATA、處理指令及實體的支持

HTML 1.0、2.0 XML的HTML擴展,添加了對HTML特有元素及實體的支持

Views 2.0 基于某些樣式完成文檔的格式化

StyleSheets 2.0 將樣式表關(guān)聯(lián)到文檔

CSS 2.0 對層疊樣式表1級的支持

CSS2 2.0 對層疊樣式表2級的支持

Events 2.0,3.0 常規(guī)的DOM事件

UIEvents 2.0,3.0 用戶界面事件

MouseEvents 2.0,3.0 由鼠標引發(fā)的事件(click、mouseover等)

MutationEvents 2.0,3.0 DOM樹變化時引發(fā)的事件

HTMLEvents 2.0 HTML4.01事件

Range 2.0 用于操作DOM樹中某個范圍的對象和方法

Traversal 2.0 遍歷DOM樹的方法

LS 3.0 文件與DOM樹之間的同步加載和保存

LS-Async 3.0 文件與DOM樹之間的異步加載和保存

Validation 3.0 在確保有效的前提下修改DOM樹的方法

盡管使用 hasFeature()確實方便,但也有缺點。因為實現(xiàn)者可以自行決定是否與 DOM 規(guī)范的不

同部分保持一致。事實上,要想讓 hasFearture()方法針對所有值都返回 true 很容易,但返回 true

有時候也不意味著實現(xiàn)與規(guī)范一致。例如,Safari 2.x 及更早版本會在沒有完全實現(xiàn)某些 DOM 功能的情

況下也返回 true。為此,我們建議多數(shù)情況下,在使用 DOM 的某些特殊的功能之前,最好除了檢測

hasFeature()之外,還同時使用能力檢測。

6. 文檔寫入

有一個 document 對象的功能已經(jīng)存在很多年了,那就是將輸出流寫入到網(wǎng)頁中的能力。這個能力

體現(xiàn)在下列 4 個方法中:write()、writeln()、open()和 close()。其中,write()和 writeln()

方法都接受一個字符串參數(shù),即要寫入到輸出流中的文本。write()會原樣寫入,而 writeln()則會

在字符串的末尾添加一個換行符(\

)。在頁面被加載的過程中,可以使用這兩個方法向頁面中動態(tài)地

加入內(nèi)容,如下面的例子所示。

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

第278頁

260 第 10 章 DOM

<html>

<head>

<title>document.write() Example</title>

</head>

<body>

<p>The current date and time is:

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

document.write(\"<strong>\" + (new Date()).toString() + \"</strong>\");

</script>

</p>

</body>

</html>

DocumentWriteExample01.htm

這個例子展示了在頁面加載過程中輸出當前日期和時間的代碼。其中,日期被包含在一個<strong>

元素中,就像在 HTML 頁面中包含普通的文本一樣。這樣做會創(chuàng)建一個 DOM 元素,而且可以在將來訪

問該元素。通過 write()和 writeln()輸出的任何 HTML 代碼都將如此處理。

此外,還可以使用 write()和 writeln()方法動態(tài)地包含外部資源,例如 JavaScript 文件等。在包

含 JavaScript 文件時,必須注意不能像下面的例子那樣直接包含字符串\"</script>\",因為這會導致該

字符串被解釋為腳本塊的結(jié)束,它后面的代碼將無法執(zhí)行。

<html>

<head>

<title>document.write() Example 2</title>

</head>

<body>

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

document.write(\"<script type=\\\"text/javascript\\\" src=\\\"file.js\\\">\" +

\"</script>\");

</script>

</body>

</html>

DocumentWriteExample02.htm

即使這個文件看起來沒錯,但字符串\"</script>\"將被解釋為與外部的<script>標簽匹配,結(jié)果

文本\");將會出現(xiàn)在頁面中。為避免這個問題,只需加入轉(zhuǎn)義字符\\即可;第 2 章也曾經(jīng)提及這個問題,

解決方案如下。

<html>

<head>

<title>document.write() Example 3</title>

</head>

<body>

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

document.write(\"<script type=\\\"text/javascript\\\" src=\\\"file.js\\\">\" +

\"<\\/script>\");

</script>

</body>

</html>

DocumentWriteExample03.htm

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

第279頁

10.1 節(jié)點層次 261

1

2

3

4

5

13

6

7

8

9

10

11

12

字符串\"<\\/script>\"不會被當作外部<script>標簽的關(guān)閉標簽,因而頁面中也就不會出現(xiàn)多余

的內(nèi)容了。

前面的例子使用 document.write()在頁面被呈現(xiàn)的過程中直接向其中輸出了內(nèi)容。如果在文檔

加載結(jié)束后再調(diào)用 document.write(),那么輸出的內(nèi)容將會重寫整個頁面,如下面的例子所示:

<html>

<head>

<title>document.write() Example 4</title>

</head>

<body>

<p>This is some content that you won't get to see because it will be overwritten.</p>

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

window.onload = function(){

document.write(\"Hello world!\");

};

</script>

</body>

</html>

DocumentWriteExample04.htm

在這個例子中,我們使用了 window.onload 事件處理程序(事件將在第 13 章討論),等到頁面完

全加載之后延遲執(zhí)行函數(shù)。函數(shù)執(zhí)行之后,字符串\"Hello world!\"會重寫整個頁面內(nèi)容。

方法 open()和 close()分別用于打開和關(guān)閉網(wǎng)頁的輸出流。如果是在頁面加載期間使用 write()

或 writeln()方法,則不需要用到這兩個方法。

嚴格型 XHTML 文檔不支持文檔寫入。對于那些按照 application/xml+xhtml

內(nèi)容類型提供的頁面,這兩個方法也同樣無效。

10.1.3 Element類型

除了 Document 類型之外,Element 類型就要算是 Web 編程中最常用的類型了。Element 類型用

于表現(xiàn) XML 或 HTML元素,提供了對元素標簽名、子節(jié)點及特性的訪問。Element 節(jié)點具有以下特征:

? nodeType 的值為 1;

? nodeName 的值為元素的標簽名;

? nodeValue 的值為 null;

? parentNode 可能是 Document 或 Element;

? 其子節(jié)點可能是 Element、Text、Comment、ProcessingInstruction、CDATASection 或

EntityReference。

要訪問元素的標簽名,可以使用 nodeName 屬性,也可以使用 tagName 屬性;這兩個屬性會返回

相同的值(使用后者主要是為了清晰起見)。以下面的元素為例:

<div id=\"myDiv\"></div>

可以像下面這樣取得這個元素及其標簽名:

var div = document.getElementById(\"myDiv\");

alert(div.tagName); //\"DIV\"

alert(div.tagName == div.nodeName); //true

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

第280頁

262 第 10 章 DOM

這里的元素標簽名是 div,它擁有一個值為\"myDiv\"的 ID??墒?,div.tagName 實際上輸出的是

\"DIV\"而非\"div\"。在 HTML 中,標簽名始終都以全部大寫表示;而在 XML(有時候也包括 XHTML)

中,標簽名則始終會與源代碼中的保持一致。假如你不確定自己的腳本將會在 HTML 還是 XML 文檔中

執(zhí)行,最好是在比較之前將標簽名轉(zhuǎn)換為相同的大小寫形式,如下面的例子所示:

if (element.tagName == \"div\"){ //不能這樣比較,很容易出錯!

//在此執(zhí)行某些操作

}

if (element.tagName.toLowerCase() == \"div\"){ //這樣最好(適用于任何文檔)

//在此執(zhí)行某些操作

}

這個例子展示了圍繞 tagName 屬性的兩次比較操作。第一次比較非常容易出錯,因為其代碼在

HTML 文檔中不管用。第二次比較將標簽名轉(zhuǎn)換成了全部小寫,是我們推薦的做法,因為這種做法適用

于 HTML 文檔,也適用于 XML 文檔。

可以在任何瀏覽器中通過腳本訪問 Element 類型的構(gòu)造函數(shù)及原型,包括 IE8 及

之前版本。在 Safari 2 之前版本和 Opera 8 之前的版本中,不能訪問 Element 類型的構(gòu)

造函數(shù)。

1. HTML 元素

所有 HTML 元素都由 HTMLElement 類型表示,不是直接通過這個類型,也是通過它的子類型來表

示。HTMLElement 類型直接繼承自Element 并添加了一些屬性。添加的這些屬性分別對應于每個 HTML

元素中都存在的下列標準特性。

? id,元素在文檔中的唯一標識符。

? title,有關(guān)元素的附加說明信息,一般通過工具提示條顯示出來。

? lang,元素內(nèi)容的語言代碼,很少使用。

? dir,語言的方向,值為\"ltr\"(left-to-right,從左至右)或\"rtl\"(right-to-left,從右至左),

也很少使用。

? className,與元素的class 特性對應,即為元素指定的CSS類。沒有將這個屬性命名為class,

是因為 class 是 ECMAScript 的保留字(有關(guān)保留字的信息,請參見第 1 章)。

上述這些屬性都可以用來取得或修改相應的特性值。以下面的 HTML 元素為例:

<div id=\"myDiv\" class=\"bd\" title=\"Body text\" lang=\"en\" dir=\"ltr\"></div>

HTMLElementsExample01.htm

元素中指定的所有信息,都可以通過下列 JavaScript 代碼取得:

var div = document.getElementById(\"myDiv\");

alert(div.id); //\"myDiv\"\"

alert(div.className); //\"bd\"

alert(div.title); //\"Body text\"

alert(div.lang); //\"en\"

alert(div.dir); //\"ltr\"

當然,像下面這樣通過為每個屬性賦予新的值,也可以修改對應的每個特性:

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

第281頁

10.1 節(jié)點層次 263

1

2

3

4

5

13

6

7

8

9

10

11

12

div.id = \"someOtherId\";

div.className = \"ft\";

div.title = \"Some other text\";

div.lang = \"fr\";

div.dir =\"rtl\";

HTMLElementsExample01.htm

并不是對所有屬性的修改都會在頁面中直觀地表現(xiàn)出來。對 id 或 lang 的修改對用戶而言是透明

不可見的(假設(shè)沒有基于它們的值設(shè)置的 CSS 樣式),而對 title 的修改則只會在鼠標移動到這個元素

之上時才會顯示出來。對 dir 的修改會在屬性被重寫的那一刻,立即影響頁面中文本的左、右對齊方式。

修改 className 時,如果新類關(guān)聯(lián)了與此前不同的 CSS 樣式,那么就會立即應用新的樣式。

前面提到過,所有 HTML 元素都是由 HTMLElement 或者其更具體的子類型來表示的。下表列出了

所有 HTML 元素以及與之關(guān)聯(lián)的類型(以斜體印刷的元素表示已經(jīng)不推薦使用了)。注意,表中的這些

類型在 Opera、Safari、Chrome 和 Firefox 中都可以通過 JavaScript 訪問,但在 IE8 之前的版本中不能通

過 JavaScript 訪問。

元 素 類 型 元 素 類 型

A HTMLAnchorElement EM HTMLElement

ABBR HTMLElement FIELDSET HTMLFieldSetElement

ACRONYM HTMLElement FONT HTMLFontElement

ADDRESS HTMLElement FORM HTMLFormElement

APPLET HTMLAppletElement FRAME HTMLFrameElement

AREA HTMLAreaElement FRAMESET HTMLFrameSetElement

B HTMLElement H1 HTMLHeadingElement

BASE HTMLBaseElement H2 HTMLHeadingElement

BASEFONT HTMLBaseFontElement H3 HTMLHeadingElement

BDO HTMLElement H4 HTMLHeadingElement

BIG HTMLElement H5 HTMLHeadingElement

BLOCKQUOTE HTMLQuoteElement H6 HTMLHeadingElement

BODY HTMLBodyElement HEAD HTMLHeadElement

BR HTMLBRElement HR HTMLHRElement

BUTTON HTMLButtonElement HTML HTMLHtmlElement

CAPTION HTMLTableCaptionElement I HTMLElement

CENTER HTMLElement IFRAME HTMLIFrameElement

CITE HTMLElement IMG HTMLImageElement

CODE HTMLElement INPUT HTMLInputElement

COL HTMLTableColElement INS HTMLModElement

COLGROUP HTMLTableColElement ISINDEX HTMLIsIndexElement

DD HTMLElement KBD HTMLElement

DEL HTMLModElement LABEL HTMLLabelElement

DFN HTMLElement LEGEND HTMLLegendElement

DIR HTMLDirectoryElement LI HTMLLIElement

DIV HTMLDivElement LINK HTMLLinkElement

DL HTMLDListElement MAP HTMLMapElement

DT HTMLElement MENU HTMLMenuElement

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

第282頁

264 第 10 章 DOM

(續(xù))

元 素 類 型 元 素 類 型

META HTMLMetaElement STRONG HTMLElement

NOFRAMES HTMLElement STYLE HTMLStyleElement

NOSCRIPT HTMLElement SUB HTMLElement

OBJECT HTMLObjectElement SUP HTMLElement

OL HTMLOListElement TABLE HTMLTableElement

OPTGROUP HTMLOptGroupElement TBODY HTMLTableSectionElement

OPTION HTMLOptionElement TD HTMLTableCellElement

P HTMLParagraphElement TEXTAREA HTMLTextAreaElement

PARAM HTMLParamElement TFOOT HTMLTableSectionElement

PRE HTMLPreElement TH HTMLTableCellElement

Q HTMLQuoteElement THEAD HTMLTableSectionElement

S HTMLElement TITLE HTMLTitleElement

SAMP HTMLElement TR HTMLTableRowElement

SCRIPT HTMLScriptElement TT HTMLElement

SELECT HTMLSelectElement U HTMLElement

SMALL HTMLElement UL HTMLUListElement

SPAN HTMLElement VAR HTMLElement

STRIKE HTMLElement

表中的每一種類型都有與之相關(guān)的特性和方法。本書將會討論其中很多類型。

2. 取得特性

每個元素都有一或多個特性,這些特性的用途是給出相應元素或其內(nèi)容的附加信息。操作特性的

DOM 方法主要有三個,分別是 getAttribute()、setAttribute()和 removeAttribute()。這三

個方法可以針對任何特性使用,包括那些以 HTMLElement 類型屬性的形式定義的特性。來看下面的例子:

var div = document.getElementById(\"myDiv\");

alert(div.getAttribute(\"id\")); //\"myDiv\"

alert(div.getAttribute(\"class\")); //\"bd\"

alert(div.getAttribute(\"title\")); //\"Body text\"

alert(div.getAttribute(\"lang\")); //\"en\"

alert(div.getAttribute(\"dir\")); //\"ltr\"

注意,傳遞給 getAttribute()的特性名與實際的特性名相同。因此要想得到 class 特性值,應

該傳入\"class\"而不是\"className\",后者只有在通過對象屬性訪問特性時才用。如果給定名稱的特性

不存在,getAttribute()返回 null。

通過 getAttribute()方法也可以取得自定義特性(即標準 HTML 語言中沒有的特性)的值,以

下面的元素為例:

<div id=\"myDiv\" my_special_attribute=\"hello!\"></div>

這個元素包含一個名為 my_special_attribute 的自定義特性,它的值是\"hello!\"??梢韵袢?/p>

得其他特性一樣取得這個值,如下所示:

var value = div.getAttribute(\"my_special_attribute\");

不過,特性的名稱是不區(qū)分大小寫的,即\"ID\"和\"id\"代表的都是同一個特性。另外也要注意,根

據(jù) HTML5 規(guī)范,自定義特性應該加上 data-前綴以便驗證。

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

第283頁

10.1 節(jié)點層次 265

1

2

3

4

5

13

6

7

8

9

10

11

12

任何元素的所有特性,也都可以通過 DOM 元素本身的屬性來訪問。當然,HTMLElement 也會有 5

個屬性與相應的特性一一對應。不過,只有公認的(非自定義的)特性才會以屬性的形式添加到 DOM

對象中。以下面的元素為例:

<div id=\"myDiv\" align=\"left\" my_special_attribute=\"hello!\"></div>

因為 id 和 align 在 HTML 中是<div>的公認特性,因此該元素的 DOM 對象中也將存在對應的屬

性。不過,自定義特性 my_special_attribute 在 Safari、Opera、Chrome 及 Firefox 中是不存在的;

但 IE 卻會為自定義特性也創(chuàng)建屬性,如下面的例子所示:

alert(div.id); //\"myDiv\"

alert(div.my_special_attribute); //undefined(IE 除外)

alert(div.align); //\"left\"

ElementAttributesExample02.htm

有兩類特殊的特性,它們雖然有對應的屬性名,但屬性的值與通過 getAttribute()返回的值并不

相同。第一類特性就是 style,用于通過 CSS 為元素指定樣式。在通過 getAttribute()訪問時,返

回的 style 特性值中包含的是 CSS 文本,而通過屬性來訪問它則會返回一個對象。由于 style 屬性是

用于以編程方式訪問元素樣式的(本章后面討論),因此并沒有直接映射到 style 特性。

第二類與眾不同的特性是 onclick 這樣的事件處理程序。當在元素上使用時,onclick 特性中包

含的是 JavaScript 代碼,如果通過 getAttribute()訪問,則會返回相應代碼的字符串。而在訪問

onclick 屬性時,則會返回一個 JavaScript 函數(shù)(如果未在元素中指定相應特性,則返回 null)。這是

因為 onclick 及其他事件處理程序?qū)傩员旧砭蛻摫毁x予函數(shù)值。

由于存在這些差別,在通過 JavaScript 以編程方式操作 DOM 時,開發(fā)人員經(jīng)常不使用 getAttribute(),而是只使用對象的屬性。只有在取得自定義特性值的情況下,才會使用 getAttribute()方法。

在 IE7及以前版本中,通過 getAttribute()方法訪問 style 特性或 onclick 這樣

的事件處理特性時,返回的值與屬性的值相同。換句話說,getAttribute(\"style\")返

回一個對象,而 getAttribute(\"onclick\")返回一個函數(shù)。雖然 IE8 已經(jīng)修復了這個

bug,但不同IE版本間的不一致性,也是導致開發(fā)人員不使用getAttribute()訪問HTML

特性的一個原因。

3. 設(shè)置特性

與 getAttribute()對應的方法是 setAttribute(),這個方法接受兩個參數(shù):要設(shè)置的特性名和

值。如果特性已經(jīng)存在,setAttribute()會以指定的值替換現(xiàn)有的值;如果特性不存在,setAttribute()

則創(chuàng)建該屬性并設(shè)置相應的值。來看下面的例子:

div.setAttribute(\"id\", \"someOtherId\");

div.setAttribute(\"class\", \"ft\");

div.setAttribute(\"title\", \"Some other text\");

div.setAttribute(\"lang\",\"fr\");

div.setAttribute(\"dir\", \"rtl\");

ElementAttributesExample01.htm

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

第284頁

266 第 10 章 DOM

通過 setAttribute()方法既可以操作 HTML 特性也可以操作自定義特性。通過這個方法設(shè)置的

特性名會被統(tǒng)一轉(zhuǎn)換為小寫形式,即\"ID\"最終會變成\"id\"。

因為所有特性都是屬性,所以直接給屬性賦值可以設(shè)置特性的值,如下所示。

div.id = \"someOtherId\";

div.align = \"left\";

不過,像下面這樣為 DOM 元素添加一個自定義的屬性,該屬性不會自動成為元素的特性。

div.mycolor = \"red\";

alert(div.getAttribute(\"mycolor\")); //null(IE 除外)

這個例子添加了一個名為 mycolor 的屬性并將它的值設(shè)置為\"red\"。在大多數(shù)瀏覽器中,這個屬

性都不會自動變成元素的特性,因此想通過 getAttribute()取得同名特性的值,結(jié)果會返回 null。

可是,自定義屬性在 IE 中會被當作元素的特性,反之亦然。

在 IE7 及以前版本中,setAttribute()存在一些異常行為。通過這個方法設(shè)置

class 和 style 特性,沒有任何效果,而使用這個方法設(shè)置事件處理程序特性時也

一樣。盡管到了 IE8 才解決這些問題,但我們還是推薦通過屬性來設(shè)置特性。

要介紹的最后一個方法是 removeAttribute(),這個方法用于徹底刪除元素的特性。調(diào)用這個方

法不僅會清除特性的值,而且也會從元素中完全刪除特性,如下所示:

div.removeAttribute(\"class\");

這個方法并不常用,但在序列化 DOM 元素時,可以通過它來確切地指定要包含哪些特性。

IE6 及以前版本不支持 removeAttribute()。

4. attributes 屬性

Element 類型是使用 attributes 屬性的唯一一個 DOM 節(jié)點類型。attributes 屬性中包含一個

NamedNodeMap,與 NodeList 類似,也是一個“動態(tài)”的集合。元素的每一個特性都由一個 Attr 節(jié)

點表示,每個節(jié)點都保存在 NamedNodeMap 對象中。NamedNodeMap 對象擁有下列方法。

? getNamedItem(name):返回 nodeName 屬性等于 name 的節(jié)點;

? removeNamedItem(name):從列表中移除 nodeName 屬性等于 name 的節(jié)點;

? setNamedItem(node):向列表中添加節(jié)點,以節(jié)點的 nodeName 屬性為索引;

? item(pos):返回位于數(shù)字 pos 位置處的節(jié)點。

attributes 屬性中包含一系列節(jié)點,每個節(jié)點的 nodeName 就是特性的名稱,而節(jié)點的 nodeValue

就是特性的值。要取得元素的 id 特性,可以使用以下代碼。

var id = element.attributes.getNamedItem(\"id\").nodeValue;

以下是使用方括號語法通過特性名稱訪問節(jié)點的簡寫方式。

var id = element.attributes[\"id\"].nodeValue;

也可以使用這種語法來設(shè)置特性的值,即先取得特性節(jié)點,然后再將其 nodeValue 設(shè)置為新值,

如下所示。

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

第285頁

10.1 節(jié)點層次 267

1

2

3

4

5

13

6

7

8

9

10

11

12

element.attributes[\"id\"].nodeValue = \"someOtherId\";

調(diào)用 removeNamedItem()方法與在元素上調(diào)用 removeAttribute()方法的效果相同——直接刪

除具有給定名稱的特性。下面的例子展示了兩個方法間唯一的區(qū)別,即 removeNamedItem()返回表示

被刪除特性的 Attr 節(jié)點。

var oldAttr = element.attributes.removeNamedItem(\"id\");

最后,setNamedItem()是一個很不常用的方法,通過這個方法可以為元素添加一個新特性,為此

需要為它傳入一個特性節(jié)點,如下所示。

element.attributes.setNamedItem(newAttr);

一般來說,由于前面介紹的 attributes 的方法不夠方便,因此開發(fā)人員更多的會使用

getAttribute()、removeAttribute()和 setAttribute()方法。

不過,如果想要遍歷元素的特性,attributes 屬性倒是可以派上用場。在需要將 DOM 結(jié)構(gòu)序列

化為 XML 或 HTML 字符串時,多數(shù)都會涉及遍歷元素特性。以下代碼展示了如何迭代元素的每一個特

性,然后將它們構(gòu)造成 name=\"value\" name=\"value\"這樣的字符串格式。

function outputAttributes(element){

var pairs = new Array(),

attrName,

attrValue,

i,

len;

for (i=0, len=element.attributes.length; i < len; i++){

attrName = element.attributes[i].nodeName;

attrValue = element.attributes[i].nodeValue;

pairs.push(attrName + \"=\\\"\" + attrValue + \"\\\"\");

}

return pairs.join(\" \");

}

ElementAttributesExample03.htm

這個函數(shù)使用了一個數(shù)組來保存名值對,最后再以空格為分隔符將它們拼接起來(這是序列化長字

符串時的一種常用技巧)。通過 attributes.length 屬性,for 循環(huán)會遍歷每個特性,將特性的名稱

和值輸出為字符串。關(guān)于以上代碼的運行結(jié)果,以下是兩點必要的說明。

? 針對 attributes 對象中的特性,不同瀏覽器返回的順序不同。這些特性在 XML 或 HTML 代

碼中出現(xiàn)的先后順序,不一定與它們出現(xiàn)在 attributes 對象中的順序一致。

? IE7 及更早的版本會返回 HTML 元素中所有可能的特性,包括沒有指定的特性。換句話說,返

回 100 多個特性的情況會很常見。

針對 IE7 及更早版本中存在的問題,可以對上面的函數(shù)加以改進,讓它只返回指定的特性。每個特

性節(jié)點都有一個名為 specified 的屬性,這個屬性的值如果為 true,則意味著要么是在 HTML 中指

定了相應特性,要么是通過 setAttribute()方法設(shè)置了該特性。在 IE 中,所有未設(shè)置過的特性的該

屬性值都為 false,而在其他瀏覽器中根本不會為這類特性生成對應的特性節(jié)點(因此,在這些瀏覽器

中,任何特性節(jié)點的 specified 值始終為 true)。改進后的代碼如下所示。

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

第286頁

268 第 10 章 DOM

function outputAttributes(element){

var pairs = new Array(),

attrName,

attrValue,

i,

len;

for (i=0, len=element.attributes.length; i < len; i++){

attrName = element.attributes[i].nodeName;

attrValue = element.attributes[i].nodeValue;

if (element.attributes[i].specified) {

pairs.push(attrName + \"=\\\"\" + attrValue + \"\\\"\");

}

}

return pairs.join(\" \");

}

ElementAttributesExample04.htm

這個經(jīng)過改進的函數(shù)可以確保即使在 IE7 及更早的版本中,也會只返回指定的特性。

5. 創(chuàng)建元素

使用 document.createElement()方法可以創(chuàng)建新元素。這個方法只接受一個參數(shù),即要創(chuàng)建元

素的標簽名。這個標簽名在 HTML 文檔中不區(qū)分大小寫,而在 XML(包括 XHTML)文檔中,則是區(qū)

分大小寫的。例如,使用下面的代碼可以創(chuàng)建一個<div>元素。

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

在使用 createElement()方法創(chuàng)建新元素的同時,也為新元素設(shè)置了 ownerDocuemnt 屬性。此

時,還可以操作元素的特性,為它添加更多子節(jié)點,以及執(zhí)行其他操作。來看下面的例子。

div.id = \"myNewDiv\";

div.className = \"box\";

在新元素上設(shè)置這些特性只是給它們賦予了相應的信息。由于新元素尚未被添加到文檔樹中,因此

設(shè)置這些特性不會影響瀏覽器的顯示。要把新元素添加到文檔樹,可以使用 appendChild()、insertBefore()或 replaceChild()方法。下面的代碼會把新創(chuàng)建的元素添加到文檔的<body>元素中。

document.body.appendChild(div);

CreateElementExample01.htm

一旦將元素添加到文檔樹中,瀏覽器就會立即呈現(xiàn)該元素。此后,對這個元素所作的任何修改都會

實時反映在瀏覽器中。

在 IE 中可以以另一種方式使用 createElement(),即為這個方法傳入完整的元素標簽,也可以包

含屬性,如下面的例子所示。

var div = document.createElement(\"<div id=\\\"myNewDiv\\\" class=\\\"box\\\"></div >\");

這種方式有助于避開在 IE7 及更早版本中動態(tài)創(chuàng)建元素的某些問題。下面是已知的一些這類問題。

? 不能設(shè)置動態(tài)創(chuàng)建的<iframe>元素的 name 特性。

? 不能通過表單的 reset()方法重設(shè)動態(tài)創(chuàng)建的<input>元素(第 13 章將討論 reset()方法)。

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

第287頁

10.1 節(jié)點層次 269

1

2

3

4

5

13

6

7

8

9

10

11

12

? 動態(tài)創(chuàng)建的 type 特性值為\"reset\"的<buttou>元素重設(shè)不了表單。

? 動態(tài)創(chuàng)建的一批 name 相同的單選按鈕彼此毫無關(guān)系。name 值相同的一組單選按鈕本來應該用

于表示同一選項的不同值,但動態(tài)創(chuàng)建的一批這種單選按鈕之間卻沒有這種關(guān)系。

上述所有問題都可以通過在createElement()中指定完整的HTML標簽來解決,如下面的例子所示。

if (client.browser.ie && client.browser.ie <=7){

//創(chuàng)建一個帶 name 特性的 iframe 元素

var iframe = document.createElement(\"<iframe name=\\\"myframe\\\"></iframe>\");

//創(chuàng)建 input 元素

var input = document.createElement(\"<input type=\\\"checkbox\\\">\");

//創(chuàng)建 button 元素

var button = document.createElement(\"<button type=\\\"reset\\\"></button>\");

//創(chuàng)建單選按鈕

var radio1 = document.createElement(\"<input type=\\\"radio\\\" name=\\\"choice\\\" \"+

\"value=\\\"1\\\">\");

var radio2 = document.createElement(\"<input type=\\\"radio\\\" name=\\\"choice\\\" \"+

\"value=\\\"2\\\">\");

}

與使用 createElement()的慣常方式一樣,這樣的用法也會返回一個 DOM 元素的引用??梢詫?/p>

這個引用添加到文檔中,也可以對其加以增強。但是,由于這樣的用法要求使用瀏覽器檢測,因此我們

建議只在需要避開 IE 及更早版本中上述某個問題的情況下使用。其他瀏覽器都不支持這種用法。

6. 元素的子節(jié)點

元素可以有任意數(shù)目的子節(jié)點和后代節(jié)點,因為元素可以是其他元素的子節(jié)點。元素的

childNodes 屬性中包含了它的所有子節(jié)點,這些子節(jié)點有可能是元素、文本節(jié)點、注釋或處理指令。

不同瀏覽器在看待這些節(jié)點方面存在顯著的不同,以下面的代碼為例。

<ul id=\"myList\">

<li>Item 1</li>

<li>Item 2</li>

<li>Item 3</li>

</ul>

如果是 IE 來解析這些代碼,那么<ul>元素會有 3 個子節(jié)點,分別是 3 個<li>元素。但如果是在其

他瀏覽器中,<ul>元素都會有 7 個元素,包括 3 個<li>元素和 4 個文本節(jié)點(表示<li>元素之間的空

白符)。如果像下面這樣將元素間的空白符刪除,那么所有瀏覽器都會返回相同數(shù)目的子節(jié)點。

<ul id=\"myList\"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>

對于這段代碼,<ul>元素在任何瀏覽器中都會包含 3 個子節(jié)點。如果需要通過 childNodes 屬性

遍歷子節(jié)點,那么一定不要忘記瀏覽器間的這一差別。這意味著在執(zhí)行某項操作以前,通常都要先檢查

一下 nodeTpye 屬性,如下面的例子所示。

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

if (element.childNodes[i].nodeType == 1){

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

}

}

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

第288頁

270 第 10 章 DOM

這個例子會循環(huán)遍歷特定元素的每一個子節(jié)點,然后只在子節(jié)點的 nodeType 等于 1(表示是元素

節(jié)點)的情況下,才會執(zhí)行某些操作。

如果想通過某個特定的標簽名取得子節(jié)點或后代節(jié)點該怎么辦呢?實際上,元素也支持

getElementsByTagName()方法。在通過元素調(diào)用這個方法時,除了搜索起點是當前元素之外,其他

方面都跟通過 document 調(diào)用這個方法相同,因此結(jié)果只會返回當前元素的后代。例如,要想取得前面

<ul>元素中包含的所有<li>元素,可以使用下列代碼。

var ul = document.getElementById(\"myList\");

var items = ul.getElementsByTagName(\"li\");

要注意的是,這里<ul>的后代中只包含直接子元素。不過,如果它包含更多層次的后代元素,那

么各個層次中包含的<li>元素也都會返回。

10.1.4 Text類型

文本節(jié)點由 Text 類型表示,包含的是可以照字面解釋的純文本內(nèi)容。純文本中可以包含轉(zhuǎn)義后的

HTML 字符,但不能包含 HTML 代碼。Text 節(jié)點具有以下特征:

? nodeType 的值為 3;

? nodeName 的值為\"#text\";

? nodeValue 的值為節(jié)點所包含的文本;

? parentNode 是一個 Element;

? 不支持(沒有)子節(jié)點。

可以通過 nodeValue 屬性或 data 屬性訪問 Text 節(jié)點中包含的文本,這兩個屬性中包含的值相

同。對 nodeValue 的修改也會通過 data 反映出來,反之亦然。使用下列方法可以操作節(jié)點中的文本。

? appendData(text):將 text 添加到節(jié)點的末尾。

? deleteData(offset, count):從 offset 指定的位置開始刪除 count 個字符。

? insertData(offset, text):在 offset 指定的位置插入 text。

? replaceData(offset, count, text):用 text 替換從 offset 指定的位置開始到 offset+

count 為止處的文本。

? splitText(offset):從 offset 指定的位置將當前文本節(jié)點分成兩個文本節(jié)點。

? substringData(offset, count):提取從 offset 指定的位置開始到 offset+count 為止

處的字符串。

除了這些方法之外,文本節(jié)點還有一個 length 屬性,保存著節(jié)點中字符的數(shù)目。而且,

nodeValue.length 和 data.length 中也保存著同樣的值。

在默認情況下,每個可以包含內(nèi)容的元素最多只能有一個文本節(jié)點,而且必須確實有內(nèi)容存在。來

看幾個例子。

<!-- 沒有內(nèi)容,也就沒有文本節(jié)點 -->

<div></div>

<!-- 有空格,因而有一個文本節(jié)點 -->

<div> </div>

<!-- 有內(nèi)容,因而有一個文本節(jié)點 -->

<div>Hello World!</div>

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

第289頁

10.1 節(jié)點層次 271

1

2

3

4

5

13

6

7

8

9

10

11

12

上面代碼給出的第一個<div>元素沒有內(nèi)容,因此也就不存在文本節(jié)點。開始與結(jié)束標簽之間只要

存在內(nèi)容,就會創(chuàng)建一個文本節(jié)點。因此,第二個<div>元素中雖然只包含一個空格,但仍然有一個文

本子節(jié)點;文本節(jié)點的 nodeValue 值是一個空格。第三個<div>也有一個文本節(jié)點,其 nodeValue 的

值為\"Hello World!\"。可以使用以下代碼來訪問這些文本子節(jié)點。

var textNode = div.firstChild; //或者 div.childNodes[0]

在取得了文本節(jié)點的引用后,就可以像下面這樣來修改它了。

div.firstChild.nodeValue = \"Some other message\";

TextNodeExample01.htm

如果這個文本節(jié)點當前存在于文檔樹中,那么修改文本節(jié)點的結(jié)果就會立即得到反映。另外,在修

改文本節(jié)點時還要注意,此時的字符串會經(jīng)過 HTML(或 XML,取決于文檔類型)編碼。換句話說,

小于號、大于號或引號都會像下面的例子一樣被轉(zhuǎn)義。

//輸出結(jié)果是\"Some &lt;strong&gt;other&lt;/strong&gt; message\"

div.firstChild.nodeValue = \"Some <strong>other</strong> message\";

TextNodeExample02.htm

應該說,這是在向 DOM 文檔中插入文本之前,先對其進行 HTML 編碼的一種有效方式。

在 IE8、Firefox、Safari、Chrome 和 Opera 中,可以通過腳本訪問 Text 類型的構(gòu)造

函數(shù)和原型。

1. 創(chuàng)建文本節(jié)點

可以使用 document.createTextNode()創(chuàng)建新文本節(jié)點,這個方法接受一個參數(shù)——要插入節(jié)點

中的文本。與設(shè)置已有文本節(jié)點的值一樣,作為參數(shù)的文本也將按照 HTML 或 XML 的格式進行編碼。

var textNode = document.createTextNode(\"<strong>Hello</strong> world!\");

在創(chuàng)建新文本節(jié)點的同時,也會為其設(shè)置 ownerDocument 屬性。不過,除非把新節(jié)點添加到文檔

樹中已經(jīng)存在的節(jié)點中,否則我們不會在瀏覽器窗口中看到新節(jié)點。下面的代碼會創(chuàng)建一個<div>元素

并向其中添加一條消息。

var element = document.createElement(\"div\");

element.className = \"message\";

var textNode = document.createTextNode(\"Hello world!\");

element.appendChild(textNode);

document.body.appendChild(element);

TextNodeExample03.htm

這個例子創(chuàng)建了一個新<div>元素并為它指定了值為\"message\"的 class 特性。然后,又創(chuàng)建了

一個文本節(jié)點,并將其添加到前面創(chuàng)建的元素中。最后一步,就是將這個元素添加到了文檔的<body>

元素中,這樣就可以在瀏覽器中看到新創(chuàng)建的元素和文本節(jié)點了。

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

第290頁

272 第 10 章 DOM

一般情況下,每個元素只有一個文本子節(jié)點。不過,在某些情況下也可能包含多個文本子節(jié)點,如

下面的例子所示。

var element = document.createElement(\"div\");

element.className = \"message\";

var textNode = document.createTextNode(\"Hello world!\");

element.appendChild(textNode);

var anotherTextNode = document.createTextNode(\"Yippee!\");

element.appendChild(anotherTextNode);

document.body.appendChild(element);

TextNodeExample04.htm

如果兩個文本節(jié)點是相鄰的同胞節(jié)點,那么這兩個節(jié)點中的文本就會連起來顯示,中間不會有空格。

2. 規(guī)范化文本節(jié)點

DOM 文檔中存在相鄰的同胞文本節(jié)點很容易導致混亂,因為分不清哪個文本節(jié)點表示哪個字符串。

另外,DOM 文檔中出現(xiàn)相鄰文本節(jié)點的情況也不在少數(shù),于是就催生了一個能夠?qū)⑾噜徫谋竟?jié)點合并

的方法。這個方法是由 Node 類型定義的(因而在所有節(jié)點類型中都存在),名叫 normalize()。如果

在一個包含兩個或多個文本節(jié)點的父元素上調(diào)用 normalize()方法,則會將所有文本節(jié)點合并成一個

節(jié)點,結(jié)果節(jié)點的 nodeValue 等于將合并前每個文本節(jié)點的 nodeValue 值拼接起來的值。來看一個

例子。

var element = document.createElement(\"div\");

element.className = \"message\";

var textNode = document.createTextNode(\"Hello world!\");

element.appendChild(textNode);

var anotherTextNode = document.createTextNode(\"Yippee!\");

element.appendChild(anotherTextNode);

document.body.appendChild(element);

alert(element.childNodes.length); //2

element.normalize();

alert(element.childNodes.length); //1

alert(element.firstChild.nodeValue); // \"Hello world!Yippee!\"

TextNodeExample05.htm

瀏覽器在解析文檔時永遠不會創(chuàng)建相鄰的文本節(jié)點。這種情況只會作為執(zhí)行 DOM 操作的結(jié)果出現(xiàn)。

在某些情況下,執(zhí)行 normalize()方法會導致 IE6 崩潰。不過,在 IE6 后來的

補丁中,可能已經(jīng)修復了這個問題(未經(jīng)證實)。IE7 及更高版本中不存在這個問題。

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

第291頁

10.1 節(jié)點層次 273

1

2

3

4

5

13

6

7

8

9

10

11

12

3. 分割文本節(jié)點

Text 類型提供了一個作用與 normalize()相反的方法:splitText()。這個方法會將一個文本節(jié)

點分成兩個文本節(jié)點,即按照指定的位置分割 nodeValue 值。原來的文本節(jié)點將包含從開始到指定位

置之前的內(nèi)容,新文本節(jié)點將包含剩下的文本。這個方法會返回一個新文本節(jié)點,該節(jié)點與原節(jié)點的

parentNode 相同。來看下面的例子。

var element = document.createElement(\"div\");

element.className = \"message\";

var textNode = document.createTextNode(\"Hello world!\");

element.appendChild(textNode);

document.body.appendChild(element);

var newNode = element.firstChild.splitText(5);

alert(element.firstChild.nodeValue); //\"Hello\"

alert(newNode.nodeValue); //\" world!\"

alert(element.childNodes.length); //2

TextNodeExample06.htm

在這個例子中,包含\"Hello world!\"的文本節(jié)點被分割為兩個文本節(jié)點,從位置 5 開始。位置 5

是\"Hello\"和\"world!\"之間的空格,因此原來的文本節(jié)點將包含字符串\"Hello\",而新文本節(jié)點將包

含文本\"world!\"(包含空格)。

分割文本節(jié)點是從文本節(jié)點中提取數(shù)據(jù)的一種常用 DOM 解析技術(shù)。

10.1.5 Comment類型

注釋在 DOM 中是通過 Comment 類型來表示的。Comment 節(jié)點具有下列特征:

? nodeType 的值為 8;

? nodeName 的值為\"#comment\";

? nodeValue 的值是注釋的內(nèi)容;

? parentNode 可能是 Document 或 Element;

? 不支持(沒有)子節(jié)點。

Comment 類型與 Text 類型繼承自相同的基類,因此它擁有除 splitText()之外的所有字符串操

作方法。與 Text 類型相似,也可以通過 nodeValue 或 data 屬性來取得注釋的內(nèi)容。

注釋節(jié)點可以通過其父節(jié)點來訪問,以下面的代碼為例。

<div id=\"myDiv\"><!--A comment --></div>

在此,注釋節(jié)點是<div>元素的一個子節(jié)點,因此可以通過下面的代碼來訪問它。

var div = document.getElementById(\"myDiv\");

var comment = div.firstChild;

alert(comment.data); //\"A comment\"

CommentNodeExample01.htm

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

第292頁

274 第 10 章 DOM

另外,使用 document.createComment()并為其傳遞注釋文本也可以創(chuàng)建注釋節(jié)點,如下面的例

子所示。

var comment = document.createComment(\"A comment \");

顯然,開發(fā)人員很少會創(chuàng)建和訪問注釋節(jié)點,因為注釋節(jié)點對算法鮮有影響。此外,瀏覽器也不會

識別位于</html>標簽后面的注釋。如果要訪問注釋節(jié)點,一定要保證它們是<html>元素的后代(即

位于<html>和</html>之間)。

在 Firefox、Safari、Chrome 和 Opera 中,可以訪問 Comment 類型的構(gòu)造函數(shù)和

原型。在 IE8 中,注釋節(jié)點被視作標簽名為\"!\"的元素。也就是說,使用

getElementsByTagName()可以取得注釋節(jié)點。盡管 IE9 沒有把注釋當成元素,但

它仍然通過一個名為 HTMLCommentElement 的構(gòu)造函數(shù)來表示注釋。

10.1.6 CDATASection類型

CDATASection 類型只針對基于 XML 的文檔,表示的是 CDATA 區(qū)域。與 Comment 類似,

CDATASection 類型繼承自 Text 類型,因此擁有除 splitText()之外的所有字符串操作方法。

CDATASection 節(jié)點具有下列特征:

? nodeType 的值為 4;

? nodeName 的值為\"#cdata-section\";

? nodeValue 的值是 CDATA 區(qū)域中的內(nèi)容;

? parentNode 可能是 Document 或 Element;

? 不支持(沒有)子節(jié)點。

CDATA 區(qū)域只會出現(xiàn)在 XML 文檔中,因此多數(shù)瀏覽器都會把 CDATA 區(qū)域錯誤地解析為 Comment

或 Element。以下面的代碼為例:

<div id=\"myDiv\"><![CDATA[This is some content.]]></div>

這個例子中的<div>元素應該包含一個 CDATASection 節(jié)點??墒?,四大主流瀏覽器無一能夠這樣

解析它。即使對于有效的 XHTML 頁面,瀏覽器也沒有正確地支持嵌入的 CDATA 區(qū)域。

在真正的 XML 文檔中,可以使用 document.createCDataSection()來創(chuàng)建 CDATA 區(qū)域,只需

為其傳入節(jié)點的內(nèi)容即可。

在 Firefox、Safari、Chrome 和 Opera 中,可以訪問 CDATASection 類型的構(gòu)造函

數(shù)和原型。IE9 及之前版本不支持這個類型。

10.1.7 DocumentType類型

DocumentType 類型在 Web 瀏覽器中并不常用,僅有 Firefox、Safari 和 Opera 支持它①。Document-

——————————

① Chrome 4.0 也支持 DocumentType 類型。

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

第293頁

10.1 節(jié)點層次 275

1

2

3

4

5

13

6

7

8

9

10

11

12

Type 包含著與文檔的 doctype 有關(guān)的所有信息,它具有下列特征:

? nodeType 的值為 10;

? nodeName 的值為 doctype 的名稱;

? nodeValue 的值為 null;

? parentNode 是 Document;

? 不支持(沒有)子節(jié)點。

在 DOM1 級中,DocumentType 對象不能動態(tài)創(chuàng)建,而只能通過解析文檔代碼的方式來創(chuàng)建。支

持它的瀏覽器會把 DocumentType 對象保存在 document.doctype 中 。 DOM1 級描述了

DocumentType 對象的 3 個屬性:name、entities 和 notations。其中,name 表示文檔類型的名稱;

entities 是由文檔類型描述的實體的 NamedNodeMap 對象;notations 是由文檔類型描述的符號的

NamedNodeMap 對象。通常,瀏覽器中的文檔使用的都是 HTML 或 XHTML 文檔類型,因而 entities

和 notations 都是空列表(列表中的項來自行內(nèi)文檔類型聲明)。但不管怎樣,只有 name 屬性是有用

的。這個屬性中保存的是文檔類型的名稱,也就是出現(xiàn)在<!DOCTYPE 之后的文本。以下面嚴格型 HTML

4.01 的文檔類型聲明為例:

<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"

\"http://www.w3.org/TR/html4/strict.dtd\">

DocumentType 的 name 屬性中保存的就是\"HTML\":

alert(document.doctype.name); //\"HTML\"

IE 及更早版本不支持 DocumentType,因此 document.doctype 的值始終都等于 null。可是,

這些瀏覽器會把文檔類型聲明錯誤地解釋為注釋,并且為它創(chuàng)建一個注釋節(jié)點。IE9 會 給

document.doctype 賦正確的對象,但仍然不支持訪問 DocumentType 類型。

10.1.8 DocumentFragment類型

在所有節(jié)點類型中,只有 DocumentFragment 在文檔中沒有對應的標記。DOM 規(guī)定文檔片段

(document fragment)是一種“輕量級”的文檔,可以包含和控制節(jié)點,但不會像完整的文檔那樣占用

額外的資源。DocumentFragment 節(jié)點具有下列特征:

? nodeType 的值為 11;

? nodeName 的值為\"#document-fragment\";

? nodeValue 的值為 null;

? parentNode 的值為 null;

? 子節(jié)點可以是 Element、ProcessingInstruction、Comment、Text、CDATASection 或

EntityReference。

雖然不能把文檔片段直接添加到文檔中,但可以將它作為一個“倉庫”來使用,即可以在里面保存將

來可能會添加到文檔中的節(jié)點。要創(chuàng)建文檔片段,可以使用 document.createDocumentFragment()方

法,如下所示:

var fragment = document.createDocumentFragment();

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

第294頁

276 第 10 章 DOM

文檔片段繼承了 Node 的所有方法,通常用于執(zhí)行那些針對文檔的 DOM 操作。如果將文檔中的節(jié)

點添加到文檔片段中,就會從文檔樹中移除該節(jié)點,也不會從瀏覽器中再看到該節(jié)點。添加到文檔片段

中的新節(jié)點同樣也不屬于文檔樹??梢酝ㄟ^ appendChild()或 insertBefore()將文檔片段中內(nèi)容添

加到文檔中。在將文檔片段作為參數(shù)傳遞給這兩個方法時,實際上只會將文檔片段的所有子節(jié)點添加到

相應位置上;文檔片段本身永遠不會成為文檔樹的一部分。來看下面的 HTML 示例代碼:

<ul id=\"myList\"></ul>

假設(shè)我們想為這個<ul>元素添加 3 個列表項。如果逐個地添加列表項,將會導致瀏覽器反復渲染(呈

現(xiàn))新信息。為避免這個問題,可以像下面這樣使用一個文檔片段來保存創(chuàng)建的列表項,然后再一次性

將它們添加到文檔中。

var fragment = document.createDocumentFragment();

var ul = document.getElementById(\"myList\");

var li = null;

for (var i=0; i < 3; i++){

li = document.createElement(\"li\");

li.appendChild(document.createTextNode(\"Item \" + (i+1)));

fragment.appendChild(li);

}

ul.appendChild(fragment);

DocumentFragmentExample01.htm

在這個例子中,我們先創(chuàng)建一個文檔片段并取得了對<ul>元素的引用。然后,通過 for 循環(huán)創(chuàng)建

3 個列表項,并通過文本表示它們的順序。為此,需要分別創(chuàng)建<li>元素、創(chuàng)建文本節(jié)點,再把文本節(jié)

點添加到<li>元素。接著使用 appendChild()將<li>元素添加到文檔片段中。循環(huán)結(jié)束后,再調(diào)用

appendChild()并傳入文檔片段,將所有列表項添加到<ul>元素中。此時,文檔片段的所有子節(jié)點都

被刪除并轉(zhuǎn)移到了<ul>元素中。

10.1.9 Attr類型

元素的特性在 DOM 中以 Attr 類型來表示。在所有瀏覽器中(包括 IE8),都可以訪問 Attr 類型

的構(gòu)造函數(shù)和原型。從技術(shù)角度講,特性就是存在于元素的 attributes 屬性中的節(jié)點。特性節(jié)點具有

下列特征:

? nodeType 的值為 2;

? nodeName 的值是特性的名稱;

? nodeValue 的值是特性的值;

? parentNode 的值為 null;

? 在 HTML 中不支持(沒有)子節(jié)點;

? 在 XML 中子節(jié)點可以是 Text 或 EntityReference。

盡管它們也是節(jié)點,但特性卻不被認為是 DOM 文檔樹的一部分。開發(fā)人員最常使用的是 getAttribute()、setAttribute()和 remveAttribute()方法,很少直接引用特性節(jié)點。

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

第295頁

10.2 DOM 操作技術(shù) 277

1

2

3

4

5

13

6

7

8

9

10

11

12

Attr 對象有 3 個屬性:name、value 和 specified。其中,name 是特性名稱(與 nodeName 的

值相同),value 是特性的值(與 nodeValue 的值相同),而 specified 是一個布爾值,用以區(qū)別特

性是在代碼中指定的,還是默認的。

使用 document.createAttribute()并傳入特性的名稱可以創(chuàng)建新的特性節(jié)點。例如,要為元素

添加 align 特性,可以使用下列代碼:

var attr = document.createAttribute(\"align\");

attr.value = \"left\";

element.setAttributeNode(attr);

alert(element.attributes[\"align\"].value); //\"left\"

alert(element.getAttributeNode(\"align\").value); //\"left\"

alert(element.getAttribute(\"align\")); //\"left\"

AttrExample01.htm

這個例子創(chuàng)建了一個新的特性節(jié)點。由于在調(diào)用 createAttribute()時已經(jīng)為 name 屬性賦了值,

所以后面就不必給它賦值了。之后,又把 value 屬性的值設(shè)置為\"left\"。為了將新創(chuàng)建的特性添加到

元素中,必須使用元素的 setAttributeNode()方法。添加特性之后,可以通過下列任何方式訪問該

特性:attributes 屬性、getAttributeNode()方法以及 getAttribute()方法。其中,attributes

和 getAttributeNode()都會返回對應特性的 Attr 節(jié)點,而 getAttribute()則只返回特性的值。

我們并不建議直接訪問特性節(jié)點。實際上,使用 getAttribute()、setAttribute()

和 removeAttribute()方法遠比操作特性節(jié)點更為方便。

10.2 DOM 操作技術(shù)

很多時候,DOM 操作都比較簡明,因此用 JavaScript 生成那些通常原本是用 HTML 代碼生成的內(nèi)

容并不麻煩。不過,也有一些時候,操作 DOM 并不像表面上看起來那么簡單。由于瀏覽器中充斥著隱

藏的陷阱和不兼容問題,用 JavaScript 代碼處理 DOM 的某些部分要比處理其他部分更復雜一些。

10.2.1 動態(tài)腳本

使用<script>元素可以向頁面中插入 JavaScript 代碼,一種方式是通過其 src 特性包含外部文件,

另一種方式就是用這個元素本身來包含代碼。而這一節(jié)要討論的動態(tài)腳本,指的是在頁面加載時不存在,

但將來的某一時刻通過修改 DOM 動態(tài)添加的腳本。跟操作 HTML 元素一樣,創(chuàng)建動態(tài)腳本也有兩種方

式:插入外部文件和直接插入 JavaScript 代碼。

動態(tài)加載的外部 JavaScript 文件能夠立即運行,比如下面的<script>元素:

<script type=\"text/javascript\" src=\"client.js\"></script>

這個<script>元素包含了第 9 章的客戶端檢測腳本。而創(chuàng)建這個節(jié)點的 DOM 代碼如下所示:

var script = document.createElement(\"script\");

script.type = \"text/javascript\";

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

第296頁

278 第 10 章 DOM

script.src = \"client.js\";

document.body.appendChild(script);

顯然,這里的 DOM 代碼如實反映了相應的 HTML 代碼。不過,在執(zhí)行最后一行代碼把<script>

元素添加到頁面中之前,是不會下載外部文件的。也可以把這個元素添加到<head>元素中,效果相同。

整個過程可以使用下面的函數(shù)來封裝:

function loadScript(url){

var script = document.createElement(\"script\");

script.type = \"text/javascript\";

script.src = url;

document.body.appendChild(script);

}

然后,就可以通過調(diào)用這個函數(shù)來加載外部的 JavaScript 文件了:

loadScript(\"client.js\");

加載完成后,就可以在頁面中的其他地方使用這個腳本了。問題只有一個:怎么知道腳本加載完成

呢?遺憾的是,并沒有什么標準方式來探知這一點。不過,與此相關(guān)的一些事件倒是可以派上用場,但

要取決于所用的瀏覽器,詳細討論請見第 13 章。

另一種指定 JavaScript 代碼的方式是行內(nèi)方式,如下面的例子所示:

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

function sayHi(){

alert(\"hi\");

}

</script>

從邏輯上講,下面的 DOM 代碼是有效的:

var script = document.createElement(\"script\");

script.type = \"text/javascript\";

script.appendChild(document.createTextNode(\"function sayHi(){alert('hi');}\"));

document.body.appendChild(script);

在 Firefox、Safari、Chrome 和 Opera 中,這些 DOM 代碼可以正常運行。但在 IE 中,則會導致錯誤。

IE 將<script>視為一個特殊的元素,不允許 DOM 訪問其子節(jié)點。不過,可以使用<script>元素的

text 屬性來指定 JavaScript 代碼,像下面的例子這樣:

var script = document.createElement(\"script\");

script.type = \"text/javascript\";

script.text = \"function sayHi(){alert('hi');}\";

document.body.appendChild(script);

DynamicScriptExample01.htm

經(jīng)過這樣修改之后的代碼可以在 IE、Firefox、Opera 和 Safari 3 及之后版本中運行。Safari 3.0 之前

的版本雖然不能正確地支持 text 屬性,但卻允許使用文本節(jié)點技術(shù)來指定代碼。如果需要兼容早期版

本的 Safari,可以使用下列代碼:

var script = document.createElement(\"script\");

script.type = \"text/javascript\";

var code = \"function sayHi(){alert('hi');}\";

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

第297頁

10.2 DOM 操作技術(shù) 279

1

2

3

4

5

13

6

7

8

9

10

11

12

try {

script.appendChild(document.createTextNode(\"code\"));

} catch (ex){

script.text = \"code\";

}

document.body.appendChild(script);

這里,首先嘗試標準的 DOM 文本節(jié)點方法,因為除了 IE(在 IE 中會導致拋出錯誤),所有瀏覽器

都支持這種方式。如果這行代碼拋出了錯誤,那么說明是 IE,于是就必須使用 text 屬性了。整個過程

可以用以下函數(shù)來表示:

function loadScriptString(code){

var script = document.createElement(\"script\");

script.type = \"text/javascript\";

try {

script.appendChild(document.createTextNode(code));

} catch (ex){

script.text = code;

}

document.body.appendChild(script);

}

下面是調(diào)用這個函數(shù)的示例:

loadScriptString(\"function sayHi(){alert('hi');}\");

DynamicScriptExample02.htm

以這種方式加載的代碼會在全局作用域中執(zhí)行,而且當腳本執(zhí)行后將立即可用。實際上,這樣執(zhí)行

代碼與在全局作用域中把相同的字符串傳遞給 eval()是一樣的。

10.2.2 動態(tài)樣式

能夠把 CSS 樣式包含到 HTML 頁面中的元素有兩個。其中,<link>元素用于包含來自外部的文件,

而<style>元素用于指定嵌入的樣式。與動態(tài)腳本類似,所謂動態(tài)樣式是指在頁面剛加載時不存在的樣

式;動態(tài)樣式是在頁面加載完成后動態(tài)添加到頁面中的。

我們以下面這個典型的<link>元素為例:

<link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\">

使用 DOM 代碼可以很容易地動態(tài)創(chuàng)建出這個元素:

var link = document.createElement(\"link\");

link.rel = \"stylesheet\";

link.type = \"text/css\";

link.href = \"style.css\";

var head = document.getElementsByTagName(\"head\")[0];

head.appendChild(link);

以上代碼在所有主流瀏覽器中都可以正常運行。需要注意的是,必須將<link>元素添加到<head>

而不是<body>元素,才能保證在所有瀏覽器中的行為一致。整個過程可以用以下函數(shù)來表示:

function loadStyles(url){

var link = document.createElement(\"link\");

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

第298頁

280 第 10 章 DOM

link.rel = \"stylesheet\";

link.type = \"text/css\";

link.href = url;

var head = document.getElementsByTagName(\"head\")[0];

head.appendChild(link);

}

調(diào)用 loadStyles()函數(shù)的代碼如下所示:

loadStyles(\"styles.css\");

加載外部樣式文件的過程是異步的,也就是加載樣式與執(zhí)行 JavaScript 代碼的過程沒有固定的次序。

一般來說,知不知道樣式已經(jīng)加載完成并不重要;不過,也存在幾種利用事件來檢測這個過程是否完成

的技術(shù),這些技術(shù)將在第 13 章討論。

另一種定義樣式的方式是使用<style>元素來包含嵌入式 CSS,如下所示:

<style type=\"text/css\">

body {

background-color: red;

}

</style>

按照相同的邏輯,下列 DOM 代碼應該是有效的:

var style = document.createElement(\"style\");

style.type = \"text/css\";

style.appendChild(document.createTextNode(\"body{background-color:red}\"));

var head = document.getElementsByTagName(\"head\")[0];

head.appendChild(style);

DynamicStyleExample01.htm

以上代碼可以在 Firefox、Safari、Chrome 和 Opera 中運行,在 IE 中則會報錯。IE 將<style>視為

一個特殊的、與<script>類似的節(jié)點,不允許訪問其子節(jié)點。事實上,IE 此時拋出的錯誤與向<script>

元素添加子節(jié)點時拋出的錯誤相同。解決 IE 中這個問題的辦法,就是訪問元素的 styleSheet 屬性,

該屬性又有一個 cssText 屬性,可以接受 CSS 代碼(第 13 章將進一步討論這兩個屬性),如下面的例

子所示。

var style = document.createElement(\"style\");

style.type = \"text/css\";

try{

style.appendChild(document.createTextNode(\"body{background-color:red}\"));

} catch (ex){

style.styleSheet.cssText = \"body{background-color:red}\";

}

var head = document.getElementsByTagName(\"head\")[0];

head.appendChild(style);

與動態(tài)添加嵌入式腳本類似,重寫后的代碼使用了 try-catch 語句來捕獲 IE 拋出的錯誤,然后再

使用針對 IE 的特殊方式來設(shè)置樣式。因此,通用的解決方案如下。

function loadStyleString(css){

var style = document.createElement(\"style\");

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

第299頁

10.2 DOM 操作技術(shù) 281

1

2

3

4

5

13

6

7

8

9

10

11

12

style.type = \"text/css\";

try{

style.appendChild(document.createTextNode(css));

} catch (ex){

style.styleSheet.cssText = css;

}

var head = document.getElementsByTagName(\"head\")[0];

head.appendChild(style);

}

DynamicStyleExample02.htm

調(diào)用這個函數(shù)的示例如下:

loadStyleString(\"body{background-color:red}\");

這種方式會實時地向頁面中添加樣式,因此能夠馬上看到變化。

如果專門針對 IE 編寫代碼,務必小心使用 styleSheet.cssText 屬性。在重用

同一個<style>元素并再次設(shè)置這個屬性時,有可能會導致瀏覽器崩潰。同樣,將

cssText 屬性設(shè)置為空字符串也可能導致瀏覽器崩潰。我們希望 IE 中的這個 bug 能

夠在將來被修復。

10.2.3 操作表格

<table>元素是 HTML 中最復雜的結(jié)構(gòu)之一。要想創(chuàng)建表格,一般都必須涉及表示表格行、單元格、

表頭等方面的標簽。由于涉及的標簽多,因而使用核心 DOM 方法創(chuàng)建和修改表格往往都免不了要編寫

大量的代碼。假設(shè)我們要使用 DOM 來創(chuàng)建下面的 HTML 表格。

<table border=\"1\" width=\"100%\">

<tbody>

<tr>

<td>Cell 1,1</td>

<td>Cell 2,1</td>

</tr>

<tr>

<td>Cell 1,2</td>

<td>Cell 2,2</td>

</tr>

</tbody>

</table>

要使用核心 DOM 方法創(chuàng)建這些元素,得需要像下面這么多的代碼:

//創(chuàng)建 table

var table = document.createElement(\"table\");

table.border = 1;

table.width = \"100%\";

//創(chuàng)建 tbody

var tbody = document.createElement(\"tbody\");

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

第300頁

282 第 10 章 DOM

table.appendChild(tbody);

//創(chuàng)建第一行

var row1 = document.createElement(\"tr\");

tbody.appendChild(row1);

var cell1_1 = document.createElement(\"td\");

cell1_1.appendChild(document.createTextNode(\"Cell 1,1\"));

row1.appendChild(cell1_1);

var cell2_1 = document.createElement(\"td\");

cell2_1.appendChild(document.createTextNode(\"Cell 2,1\"));

row1.appendChild(cell2_1);

//創(chuàng)建第二行

var row2 = document.createElement(\"tr\");

tbody.appendChild(row2);

var cell1_2 = document.createElement(\"td\");

cell1_2.appendChild(document.createTextNode(\"Cell 1,2\"));

row2.appendChild(cell1_2);

var cell2_2= document.createElement(\"td\");

cell2_2.appendChild(document.createTextNode(\"Cell 2,2\"));

row2.appendChild(cell2_2);

//將表格添加到文檔主體中

document.body.appendChild(table);

顯然,DOM 代碼很長,還有點不太好懂。為了方便構(gòu)建表格,HTML DOM 還為<table>、<tbody>

和<tr>元素添加了一些屬性和方法。

為<table>元素添加的屬性和方法如下。

? caption:保存著對<caption>元素(如果有)的指針。

? tBodies:是一個<tbody>元素的 HTMLCollection。

? tFoot:保存著對<tfoot>元素(如果有)的指針。

? tHead:保存著對<thead>元素(如果有)的指針。

? rows:是一個表格中所有行的 HTMLCollection。

? createTHead():創(chuàng)建<thead>元素,將其放到表格中,返回引用。

? createTFoot():創(chuàng)建<tfoot>元素,將其放到表格中,返回引用。

? createCaption():創(chuàng)建<caption>元素,將其放到表格中,返回引用。

? deleteTHead():刪除<thead>元素。

? deleteTFoot():刪除<tfoot>元素。

? deleteCaption():刪除<caption>元素。

? deleteRow(pos):刪除指定位置的行。

? insertRow(pos):向 rows 集合中的指定位置插入一行。

為<tbody>元素添加的屬性和方法如下。

? rows:保存著<tbody>元素中行的 HTMLCollection。

? deleteRow(pos):刪除指定位置的行。

? insertRow(pos):向 rows 集合中的指定位置插入一行,返回對新插入行的引用。

為<tr>元素添加的屬性和方法如下。

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

百萬用戶使用云展網(wǎng)進行書冊翻頁效果制作,只要您有文檔,即可一鍵上傳,自動生成鏈接和二維碼(獨立電子書),支持分享到微信和網(wǎng)站!
收藏
轉(zhuǎn)發(fā)
下載
免費制作
其他案例
更多案例
免費制作
x
{{item.desc}}
下載
{{item.title}}
{{toast}}