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

javascript-gaojichengx有目錄u

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

javascript-gaojichengx有目錄u

7.2 閉包 183 1 2 3 45 13 67 8 9 1011 12 var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()()); //\"My Object\" ThisObjectExample02.htm 代碼中突出的行展示了這個(gè)例子與前一個(gè)例子之間的不同之處。在定義匿名函數(shù)之前,我們把 this對(duì)象賦值給了一個(gè)名叫 that 的變量。而在定義了閉包之后,閉包也可以訪(fǎng)問(wèn)這個(gè)變量,因?yàn)樗俏覀冊(cè)诎瘮?shù)中特意聲名的一個(gè)變量。即使在函數(shù)返回之后,that 也仍然引用著 object,所以調(diào)用object.getNameFunc()()就返回了\"My Object\"。this 和 arguments 也存在同樣的問(wèn)題。如果想訪(fǎng)問(wèn)作用域中的 arguments 對(duì)象,必須將對(duì)該對(duì)象的引用保存到另一個(gè)閉包能夠訪(fǎng)問(wèn)的變量中。在幾種特殊情況下,this 的值可能會(huì)意外地改變。比如,下... [收起]
[展開(kāi)]
javascript-gaojichengx有目錄u
粉絲: {{bookData.followerCount}}
文本內(nèi)容
第201頁(yè)

7.2 閉包 183

1

2

3

4

5

13

6

7

8

9

10

11

12

var that = this;

return function(){

return that.name;

};

}

};

alert(object.getNameFunc()()); //\"My Object\"

ThisObjectExample02.htm

代碼中突出的行展示了這個(gè)例子與前一個(gè)例子之間的不同之處。在定義匿名函數(shù)之前,我們把 this

對(duì)象賦值給了一個(gè)名叫 that 的變量。而在定義了閉包之后,閉包也可以訪(fǎng)問(wèn)這個(gè)變量,因?yàn)樗俏覀?/p>

在包含函數(shù)中特意聲名的一個(gè)變量。即使在函數(shù)返回之后,that 也仍然引用著 object,所以調(diào)用

object.getNameFunc()()就返回了\"My Object\"。

this 和 arguments 也存在同樣的問(wèn)題。如果想訪(fǎng)問(wèn)作用域中的 arguments 對(duì)

象,必須將對(duì)該對(duì)象的引用保存到另一個(gè)閉包能夠訪(fǎng)問(wèn)的變量中。

在幾種特殊情況下,this 的值可能會(huì)意外地改變。比如,下面的代碼是修改前面例子的結(jié)果。

var name = \"The Window\";

var object = {

name : \"My Object\",

getName: function(){

return this.name;

}

};

這里的 getName()方法只簡(jiǎn)單地返回 this.name 的值。以下是幾種調(diào)用 object.getName()的

方式以及各自的結(jié)果。

object.getName(); //\"My Object\"

(object.getName)(); //\"My Object\"

(object.getName = object.getName)(); //\"The Window\",在非嚴(yán)格模式下

ThisObjectExample03.htm

第一行代碼跟平常一樣調(diào)用了 object.getName(),返回的是\"My Object\",因?yàn)?this.name

就是 object.name。第二行代碼在調(diào)用這個(gè)方法前先給它加上了括號(hào)。雖然加上括號(hào)之后,就好像只

是在引用一個(gè)函數(shù),但 this 的值得到了維持,因?yàn)?object.getName 和(object.getName)的定義

是相同的。第三行代碼先執(zhí)行了一條賦值語(yǔ)句,然后再調(diào)用賦值后的結(jié)果。因?yàn)檫@個(gè)賦值表達(dá)式的值是

函數(shù)本身,所以 this 的值不能得到維持,結(jié)果就返回了\"The Window\"。

當(dāng)然,你不大可能會(huì)像第二行和第三行代碼一樣調(diào)用這個(gè)方法。不過(guò),這個(gè)例子有助于說(shuō)明即使是

語(yǔ)法的細(xì)微變化,都有可能意外改變 this 的值。

7.2.3 內(nèi)存泄漏

由于 IE9 之前的版本對(duì) JScript 對(duì)象和 COM 對(duì)象使用不同的垃圾收集例程(第 4 章曾經(jīng)討論過(guò)),

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

第202頁(yè)

184 第 7 章 函數(shù)表達(dá)式

因此閉包在 IE 的這些版本中會(huì)導(dǎo)致一些特殊的問(wèn)題。具體來(lái)說(shuō),如果閉包的作用域鏈中保存著一個(gè)

HTML 元素,那么就意味著該元素將無(wú)法被銷(xiāo)毀。來(lái)看下面的例子。

function assignHandler(){

var element = document.getElementById(\"someElement\");

element.onclick = function(){

alert(element.id);

};

}

以上代碼創(chuàng)建了一個(gè)作為 element 元素事件處理程序的閉包,而這個(gè)閉包則又創(chuàng)建了一個(gè)循環(huán)引

用(事件將在第 13 章討論)。由于匿名函數(shù)保存了一個(gè)對(duì) assignHandler()的活動(dòng)對(duì)象的引用,因此

就會(huì)導(dǎo)致無(wú)法減少 element 的引用數(shù)。只要匿名函數(shù)存在,element 的引用數(shù)至少也是 1,因此它所

占用的內(nèi)存就永遠(yuǎn)不會(huì)被回收。不過(guò),這個(gè)問(wèn)題可以通過(guò)稍微改寫(xiě)一下代碼來(lái)解決,如下所示。

function assignHandler(){

var element = document.getElementById(\"someElement\");

var id = element.id;

element.onclick = function(){

alert(id);

};

element = null;

}

在上面的代碼中,通過(guò)把 element.id 的一個(gè)副本保存在一個(gè)變量中,并且在閉包中引用該變量消

除了循環(huán)引用。但僅僅做到這一步,還是不能解決內(nèi)存泄漏的問(wèn)題。必須要記住:閉包會(huì)引用包含函數(shù)

的整個(gè)活動(dòng)對(duì)象,而其中包含著 element。即使閉包不直接引用 element,包含函數(shù)的活動(dòng)對(duì)象中也

仍然會(huì)保存一個(gè)引用。因此,有必要把 element 變量設(shè)置為 null。這樣就能夠解除對(duì) DOM 對(duì)象的引

用,順利地減少其引用數(shù),確保正常回收其占用的內(nèi)存。

7.3 模仿塊級(jí)作用域

如前所述,JavaScript 沒(méi)有塊級(jí)作用域的概念。這意味著在塊語(yǔ)句中定義的變量,實(shí)際上是在包含

函數(shù)中而非語(yǔ)句中創(chuàng)建的,來(lái)看下面的例子。

function outputNumbers(count){

for (var i=0; i < count; i++){

alert(i);

}

alert(i); //計(jì)數(shù)

}

BlockScopeExample01.htm

這個(gè)函數(shù)中定義了一個(gè) for 循環(huán),而變量 i 的初始值被設(shè)置為 0。在 Java、C++等語(yǔ)言中,變量 i

只會(huì)在 for 循環(huán)的語(yǔ)句塊中有定義,循環(huán)一旦結(jié)束,變量 i 就會(huì)被銷(xiāo)毀??墒窃?JavaScrip 中,變量 i

是定義在 ouputNumbers()的活動(dòng)對(duì)象中的,因此從它有定義開(kāi)始,就可以在函數(shù)內(nèi)部隨處訪(fǎng)問(wèn)它。即

使像下面這樣錯(cuò)誤地重新聲明同一個(gè)變量,也不會(huì)改變它的值。

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

第203頁(yè)

7.3 模仿塊級(jí)作用域 185

1

2

3

4

5

13

6

7

8

9

10

11

12

function outputNumbers(count){

for (var i=0; i < count; i++){

alert(i);

}

var i; //重新聲明變量

alert(i); //計(jì)數(shù)

}

BlockScopeExample02.htm

JavaScript 從來(lái)不會(huì)告訴你是否多次聲明了同一個(gè)變量;遇到這種情況,它只會(huì)對(duì)后續(xù)的聲明視而不

見(jiàn)(不過(guò),它會(huì)執(zhí)行后續(xù)聲明中的變量初始化)。匿名函數(shù)可以用來(lái)模仿塊級(jí)作用域并避免這個(gè)問(wèn)題。

用作塊級(jí)作用域(通常稱(chēng)為私有作用域)的匿名函數(shù)的語(yǔ)法如下所示。

(function(){

//這里是塊級(jí)作用域

})();

以上代碼定義并立即調(diào)用了一個(gè)匿名函數(shù)。將函數(shù)聲明包含在一對(duì)圓括號(hào)中,表示它實(shí)際上是一個(gè)

函數(shù)表達(dá)式。而緊隨其后的另一對(duì)圓括號(hào)會(huì)立即調(diào)用這個(gè)函數(shù)。如果有讀者感覺(jué)這種語(yǔ)法不太好理解,

可以再看看下面這個(gè)例子。

var count = 5;

outputNumbers(count);

這里初始化了變量 count,將其值設(shè)置為 5。當(dāng)然,這里的變量是沒(méi)有必要的,因?yàn)榭梢园阎抵苯?/p>

傳給函數(shù)。為了讓代碼更簡(jiǎn)潔,我們?cè)谡{(diào)用函數(shù)時(shí)用 5 來(lái)代替變量 count,如下所示。

outputNumbers(5);

這樣做之所以可行,是因?yàn)樽兞恐徊贿^(guò)是值的另一種表現(xiàn)形式,因此用實(shí)際的值替換變量沒(méi)有問(wèn)題。

再看下面的例子。

var someFunction = function(){

//這里是塊級(jí)作用域

};

someFunction();

這個(gè)例子先定義了一個(gè)函數(shù),然后立即調(diào)用了它。定義函數(shù)的方式是創(chuàng)建一個(gè)匿名函數(shù),并把匿名

函數(shù)賦值給變量 someFunction。而調(diào)用函數(shù)的方式是在函數(shù)名稱(chēng)后面添加一對(duì)圓括號(hào),即

someFunction()。通過(guò)前面的例子我們知道,可以使用實(shí)際的值來(lái)取代變量 count,那在這里是不是

也可以用函數(shù)的值直接取代函數(shù)名呢? 然而,下面的代碼卻會(huì)導(dǎo)致錯(cuò)誤。

function(){

//這里是塊級(jí)作用域

}(); //出錯(cuò)!

這段代碼會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤,是因?yàn)?JavaScript 將 function 關(guān)鍵字當(dāng)作一個(gè)函數(shù)聲明的開(kāi)始,而函

數(shù)聲明后面不能跟圓括號(hào)。然而,函數(shù)表達(dá)式的后面可以跟圓括號(hào)。要將函數(shù)聲明轉(zhuǎn)換成函數(shù)表達(dá)式,

只要像下面這樣給它加上一對(duì)圓括號(hào)即可。

(function(){

//這里是塊級(jí)作用域

})();

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

第204頁(yè)

186 第 7 章 函數(shù)表達(dá)式

無(wú)論在什么地方,只要臨時(shí)需要一些變量,就可以使用私有作用域,例如:

function outputNumbers(count){

(function () {

for (var i=0; i < count; i++){

alert(i);

}

})();

alert(i); //導(dǎo)致一個(gè)錯(cuò)誤!

}

BlockScopeExample03.htm

在這個(gè)重寫(xiě)后的 outputNumbers()函數(shù)中,我們?cè)?for 循環(huán)外部插入了一個(gè)私有作用域。在匿名

函數(shù)中定義的任何變量,都會(huì)在執(zhí)行結(jié)束時(shí)被銷(xiāo)毀。因此,變量 i 只能在循環(huán)中使用,使用后即被銷(xiāo)毀。

而在私有作用域中能夠訪(fǎng)問(wèn)變量 count,是因?yàn)檫@個(gè)匿名函數(shù)是一個(gè)閉包,它能夠訪(fǎng)問(wèn)包含作用域中的

所有變量。

這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部,從而限制向全局作用域中添加過(guò)多的變量和函數(shù)。

一般來(lái)說(shuō),我們都應(yīng)該盡量少向全局作用域中添加變量和函數(shù)。在一個(gè)由很多開(kāi)發(fā)人員共同參與的大型

應(yīng)用程序中,過(guò)多的全局變量和函數(shù)很容易導(dǎo)致命名沖突。而通過(guò)創(chuàng)建私有作用域,每個(gè)開(kāi)發(fā)人員既可

以使用自己的變量,又不必?fù)?dān)心搞亂全局作用域。例如:

(function(){

var now = new Date();

if (now.getMonth() == 0 && now.getDate() == 1){

alert(\"Happy new year!\");

}

})();

把上面這段代碼放在全局作用域中,可以用來(lái)確定哪一天是 1 月 1 日;如果到了這一天,就會(huì)向用

戶(hù)顯示一條祝賀新年的消息。其中的變量 now 現(xiàn)在是匿名函數(shù)中的局部變量,而我們不必在全局作用域

中創(chuàng)建它。

這種做法可以減少閉包占用的內(nèi)存問(wèn)題,因?yàn)闆](méi)有指向匿名函數(shù)的引用。只要函

數(shù)執(zhí)行完畢,就可以立即銷(xiāo)毀其作用域鏈了。

7.4 私有變量

嚴(yán)格來(lái)講,JavaScript 中沒(méi)有私有成員的概念;所有對(duì)象屬性都是公有的。不過(guò),倒是有一個(gè)私有

變量的概念。任何在函數(shù)中定義的變量,都可以認(rèn)為是私有變量,因?yàn)椴荒茉诤瘮?shù)的外部訪(fǎng)問(wèn)這些變量。

私有變量包括函數(shù)的參數(shù)、局部變量和在函數(shù)內(nèi)部定義的其他函數(shù)。來(lái)看下面的例子:

function add(num1, num2){

var sum = num1 + num2;

return sum;

}

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

第205頁(yè)

7.4 私有變量 187

1

2

3

4

5

13

6

7

8

9

10

11

12

在這個(gè)函數(shù)內(nèi)部,有 3 個(gè)私有變量:num1、num2 和 sum。在函數(shù)內(nèi)部可以訪(fǎng)問(wèn)這幾個(gè)變量,但在

函數(shù)外部則不能訪(fǎng)問(wèn)它們。如果在這個(gè)函數(shù)內(nèi)部創(chuàng)建一個(gè)閉包,那么閉包通過(guò)自己的作用域鏈也可以訪(fǎng)

問(wèn)這些變量。而利用這一點(diǎn),就可以創(chuàng)建用于訪(fǎng)問(wèn)私有變量的公有方法。

我們把有權(quán)訪(fǎng)問(wèn)私有變量和私有函數(shù)的公有方法稱(chēng)為特權(quán)方法(privileged method)。有兩種在對(duì)象

上創(chuàng)建特權(quán)方法的方式。第一種是在構(gòu)造函數(shù)中定義特權(quán)方法,基本模式如下。

function MyObject(){

//私有變量和私有函數(shù)

var privateVariable = 10;

function privateFunction(){

return false;

}

//特權(quán)方法

this.publicMethod = function (){

privateVariable++;

return privateFunction();

};

}

這個(gè)模式在構(gòu)造函數(shù)內(nèi)部定義了所有私有變量和函數(shù)。然后,又繼續(xù)創(chuàng)建了能夠訪(fǎng)問(wèn)這些私有成員

的特權(quán)方法。能夠在構(gòu)造函數(shù)中定義特權(quán)方法,是因?yàn)樘貦?quán)方法作為閉包有權(quán)訪(fǎng)問(wèn)在構(gòu)造函數(shù)中定義的

所有變量和函數(shù)。對(duì)這個(gè)例子而言,變量 privateVariable 和函數(shù) privateFunction()只能通過(guò)特

權(quán)方法 publicMethod()來(lái)訪(fǎng)問(wèn)。在創(chuàng)建 MyObject 的實(shí)例后,除了使用 publicMethod()這一個(gè)途

徑外,沒(méi)有任何辦法可以直接訪(fǎng)問(wèn) privateVariable 和 privateFunction()。

利用私有和特權(quán)成員,可以隱藏那些不應(yīng)該被直接修改的數(shù)據(jù),例如:

function Person(name){

this.getName = function(){

return name;

};

this.setName = function (value) {

name = value;

};

}

var person = new Person(\"Nicholas\");

alert(person.getName()); //\"Nicholas\"

person.setName(\"Greg\");

alert(person.getName()); //\"Greg\"

PrivilegedMethodExample01.htm

以上代碼的構(gòu)造函數(shù)中定義了兩個(gè)特權(quán)方法:getName()和 setName()。這兩個(gè)方法都可以在構(gòu)

造函數(shù)外部使用,而且都有權(quán)訪(fǎng)問(wèn)私有變量 name。但在 Person 構(gòu)造函數(shù)外部,沒(méi)有任何辦法訪(fǎng)問(wèn) name。

由于這兩個(gè)方法是在構(gòu)造函數(shù)內(nèi)部定義的,它們作為閉包能夠通過(guò)作用域鏈訪(fǎng)問(wèn) name。私有變量 name

在 Person 的每一個(gè)實(shí)例中都不相同,因?yàn)槊看握{(diào)用構(gòu)造函數(shù)都會(huì)重新創(chuàng)建這兩個(gè)方法。不過(guò),在構(gòu)造

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

第206頁(yè)

188 第 7 章 函數(shù)表達(dá)式

函數(shù)中定義特權(quán)方法也有一個(gè)缺點(diǎn),那就是你必須使用構(gòu)造函數(shù)模式來(lái)達(dá)到這個(gè)目的。第 6 章曾經(jīng)討論

過(guò),構(gòu)造函數(shù)模式的缺點(diǎn)是針對(duì)每個(gè)實(shí)例都會(huì)創(chuàng)建同樣一組新方法,而使用靜態(tài)私有變量來(lái)實(shí)現(xiàn)特權(quán)方

法就可以避免這個(gè)問(wèn)題。

7.4.1 靜態(tài)私有變量

通過(guò)在私有作用域中定義私有變量或函數(shù),同樣也可以創(chuàng)建特權(quán)方法,其基本模式如下所示。

(function(){

//私有變量和私有函數(shù)

var privateVariable = 10;

function privateFunction(){

return false;

}

//構(gòu)造函數(shù)

MyObject = function(){

};

//公有/特權(quán)方法

MyObject.prototype.publicMethod = function(){

privateVariable++;

return privateFunction();

};

})();

這個(gè)模式創(chuàng)建了一個(gè)私有作用域,并在其中封裝了一個(gè)構(gòu)造函數(shù)及相應(yīng)的方法。在私有作用域中,

首先定義了私有變量和私有函數(shù),然后又定義了構(gòu)造函數(shù)及其公有方法。公有方法是在原型上定義的,

這一點(diǎn)體現(xiàn)了典型的原型模式。需要注意的是,這個(gè)模式在定義構(gòu)造函數(shù)時(shí)并沒(méi)有使用函數(shù)聲明,而是

使用了函數(shù)表達(dá)式。函數(shù)聲明只能創(chuàng)建局部函數(shù),但那并不是我們想要的。出于同樣的原因,我們也沒(méi)

有在聲明 MyObject 時(shí)使用 var 關(guān)鍵字。記?。撼跏蓟唇?jīng)聲明的變量,總是會(huì)創(chuàng)建一個(gè)全局變量。

因此,MyObject 就成了一個(gè)全局變量,能夠在私有作用域之外被訪(fǎng)問(wèn)到。但也要知道,在嚴(yán)格模式下

給未經(jīng)聲明的變量賦值會(huì)導(dǎo)致錯(cuò)誤。

這個(gè)模式與在構(gòu)造函數(shù)中定義特權(quán)方法的主要區(qū)別,就在于私有變量和函數(shù)是由實(shí)例共享的。由于

特權(quán)方法是在原型上定義的,因此所有實(shí)例都使用同一個(gè)函數(shù)。而這個(gè)特權(quán)方法,作為一個(gè)閉包,總是

保存著對(duì)包含作用域的引用。來(lái)看一看下面的代碼。

(function(){

var name = \"\";

Person = function(value){

name = value;

};

Person.prototype.getName = function(){

return name;

};

Person.prototype.setName = function (value){

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

第207頁(yè)

7.4 私有變量 189

1

2

3

4

5

13

6

7

8

9

10

11

12

name = value;

};

})();

var person1 = new Person(\"Nicholas\");

alert(person1.getName()); //\"Nicholas\"

person1.setName(\"Greg\");

alert(person1.getName()); //\"Greg\"

var person2 = new Person(\"Michael\");

alert(person1.getName()); //\"Michael\"

alert(person2.getName()); //\"Michael\"

PrivilegedMethodExample02.htm

這個(gè)例子中的 Person 構(gòu)造函數(shù)與getName()和setName()方法一樣,都有權(quán)訪(fǎng)問(wèn)私有變量name。

在這種模式下,變量 name 就變成了一個(gè)靜態(tài)的、由所有實(shí)例共享的屬性。也就是說(shuō),在一個(gè)實(shí)例上調(diào)

用 setName()會(huì)影響所有實(shí)例。而調(diào)用 setName()或新建一個(gè) Person 實(shí)例都會(huì)賦予 name 屬性一個(gè)

新值。結(jié)果就是所有實(shí)例都會(huì)返回相同的值。

以這種方式創(chuàng)建靜態(tài)私有變量會(huì)因?yàn)槭褂迷投鲞M(jìn)代碼復(fù)用,但每個(gè)實(shí)例都沒(méi)有自己的私有變

量。到底是使用實(shí)例變量,還是靜態(tài)私有變量,最終還是要視你的具體需求而定。

多查找作用域鏈中的一個(gè)層次,就會(huì)在一定程度上影響查找速度。而這正是使用

閉包和私有變量的一個(gè)顯明的不足之處。

7.4.2 模塊模式

前面的模式是用于為自定義類(lèi)型創(chuàng)建私有變量和特權(quán)方法的。而道格拉斯所說(shuō)的模塊模式(module

pattern)則是為單例創(chuàng)建私有變量和特權(quán)方法。所謂單例(singleton),指的就是只有一個(gè)實(shí)例的對(duì)象。

按照慣例,JavaScript 是以對(duì)象字面量的方式來(lái)創(chuàng)建單例對(duì)象的。

var singleton = {

name : value,

method : function () {

//這里是方法的代碼

}

};

模塊模式通過(guò)為單例添加私有變量和特權(quán)方法能夠使其得到增強(qiáng),其語(yǔ)法形式如下:

var singleton = function(){

//私有變量和私有函數(shù)

var privateVariable = 10;

function privateFunction(){

return false;

}

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

第208頁(yè)

190 第 7 章 函數(shù)表達(dá)式

//特權(quán)/公有方法和屬性

return {

publicProperty: true,

publicMethod : function(){

privateVariable++;

return privateFunction();

}

};

}();

這個(gè)模塊模式使用了一個(gè)返回對(duì)象的匿名函數(shù)。在這個(gè)匿名函數(shù)內(nèi)部,首先定義了私有變量和函數(shù)。

然后,將一個(gè)對(duì)象字面量作為函數(shù)的值返回。返回的對(duì)象字面量中只包含可以公開(kāi)的屬性和方法。由于

這個(gè)對(duì)象是在匿名函數(shù)內(nèi)部定義的,因此它的公有方法有權(quán)訪(fǎng)問(wèn)私有變量和函數(shù)。從本質(zhì)上來(lái)講,這個(gè)

對(duì)象字面量定義的是單例的公共接口。這種模式在需要對(duì)單例進(jìn)行某些初始化,同時(shí)又需要維護(hù)其私有

變量時(shí)是非常有用的,例如:

var application = function(){

//私有變量和函數(shù)

var components = new Array();

//初始化

components.push(new BaseComponent());

//公共

return {

getComponentCount : function(){

return components.length;

},

registerComponent : function(component){

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

components.push(component);

}

}

};

}();

ModulePatternExample01.htm

在 Web 應(yīng)用程序中,經(jīng)常需要使用一個(gè)單例來(lái)管理應(yīng)用程序級(jí)的信息。這個(gè)簡(jiǎn)單的例子創(chuàng)建了一

個(gè)用于管理組件的 application 對(duì)象。在創(chuàng)建這個(gè)對(duì)象的過(guò)程中,首先聲明了一個(gè)私有的 components

數(shù)組,并向數(shù)組中添加了一個(gè) BaseComponent 的新實(shí)例(在這里不需要關(guān)心 BaseComponent 的代碼,我

們只是用它來(lái)展示初始化操作)。而返回對(duì)象的 getComponentCount()和 registerComponent()方法,都

是有權(quán)訪(fǎng)問(wèn)數(shù)組 components 的特權(quán)方法。前者只是返回已注冊(cè)的組件數(shù)目,后者用于注冊(cè)新組件。

簡(jiǎn)言之,如果必須創(chuàng)建一個(gè)對(duì)象并以某些數(shù)據(jù)對(duì)其進(jìn)行初始化,同時(shí)還要公開(kāi)一些能夠訪(fǎng)問(wèn)這些私有

數(shù)據(jù)的方法,那么就可以使用模塊模式。以這種模式創(chuàng)建的每個(gè)單例都是 Object 的實(shí)例,因?yàn)樽罱K要通

過(guò)一個(gè)對(duì)象字面量來(lái)表示它。事實(shí)上,這也沒(méi)有什么;畢竟,單例通常都是作為全局對(duì)象存在的,我們不

會(huì)將它傳遞給一個(gè)函數(shù)。因此,也就沒(méi)有什么必要使用 instanceof 操作符來(lái)檢查其對(duì)象類(lèi)型了。

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

第209頁(yè)

7.4 私有變量 191

1

2

3

4

5

13

6

7

8

9

10

11

12

7.4.3 增強(qiáng)的模塊模式

有人進(jìn)一步改進(jìn)了模塊模式,即在返回對(duì)象之前加入對(duì)其增強(qiáng)的代碼。這種增強(qiáng)的模塊模式適合那

些單例必須是某種類(lèi)型的實(shí)例,同時(shí)還必須添加某些屬性和(或)方法對(duì)其加以增強(qiáng)的情況。來(lái)看下面

的例子。

var singleton = function(){

//私有變量和私有函數(shù)

var privateVariable = 10;

function privateFunction(){

return false;

}

//創(chuàng)建對(duì)象

var object = new CustomType();

//添加特權(quán)/公有屬性和方法

object.publicProperty = true;

object.publicMethod = function(){

privateVariable++;

return privateFunction();

};

//返回這個(gè)對(duì)象

return object;

}();

如果前面演示模塊模式的例子中的 application 對(duì)象必須是 BaseComponent 的實(shí)例,那么就可

以使用以下代碼。

var application = function(){

//私有變量和函數(shù)

var components = new Array();

//初始化

components.push(new BaseComponent());

//創(chuàng)建 application 的一個(gè)局部副本

var app = new BaseComponent();

//公共接口

app.getComponentCount = function(){

return components.length;

};

app.registerComponent = function(component){

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

components.push(component);

}

};

//返回這個(gè)副本

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

第210頁(yè)

192 第 7 章 函數(shù)表達(dá)式

return app;

}();

ModuleAugmentationPatternExample01.htm

在這個(gè)重寫(xiě)后的應(yīng)用程序(application)單例中,首先也是像前面例子中一樣定義了私有變量。主

要的不同之處在于命名變量 app 的創(chuàng)建過(guò)程,因?yàn)樗仨毷?BaseComponent 的實(shí)例。這個(gè)實(shí)例實(shí)際上

是 application 對(duì)象的局部變量版。此后,我們又為 app 對(duì)象添加了能夠訪(fǎng)問(wèn)私有變量的公有方法。

最后一步是返回 app 對(duì)象,結(jié)果仍然是將它賦值給全局變量 application。

7.5 小結(jié)

在 JavaScript 編程中,函數(shù)表達(dá)式是一種非常有用的技術(shù)。使用函數(shù)表達(dá)式可以無(wú)須對(duì)函數(shù)命名,

從而實(shí)現(xiàn)動(dòng)態(tài)編程。匿名函數(shù),也稱(chēng)為拉姆達(dá)函數(shù),是一種使用 JavaScript 函數(shù)的強(qiáng)大方式。以下總結(jié)

了函數(shù)表達(dá)式的特點(diǎn)。

? 函數(shù)表達(dá)式不同于函數(shù)聲明。函數(shù)聲明要求有名字,但函數(shù)表達(dá)式不需要。沒(méi)有名字的函數(shù)表

達(dá)式也叫做匿名函數(shù)。

? 在無(wú)法確定如何引用函數(shù)的情況下,遞歸函數(shù)就會(huì)變得比較復(fù)雜;

? 遞歸函數(shù)應(yīng)該始終使用 arguments.callee 來(lái)遞歸地調(diào)用自身,不要使用函數(shù)名——函數(shù)名可

能會(huì)發(fā)生變化。

當(dāng)在函數(shù)內(nèi)部定義了其他函數(shù)時(shí),就創(chuàng)建了閉包。閉包有權(quán)訪(fǎng)問(wèn)包含函數(shù)內(nèi)部的所有變量,原理

如下。

? 在后臺(tái)執(zhí)行環(huán)境中,閉包的作用域鏈包含著它自己的作用域、包含函數(shù)的作用域和全局作用域。

? 通常,函數(shù)的作用域及其所有變量都會(huì)在函數(shù)執(zhí)行結(jié)束后被銷(xiāo)毀。

? 但是,當(dāng)函數(shù)返回了一個(gè)閉包時(shí),這個(gè)函數(shù)的作用域?qū)?huì)一直在內(nèi)存中保存到閉包不存在為止。

使用閉包可以在 JavaScript 中模仿塊級(jí)作用域(JavaScript 本身沒(méi)有塊級(jí)作用域的概念),要點(diǎn)如下。

? 創(chuàng)建并立即調(diào)用一個(gè)函數(shù),這樣既可以執(zhí)行其中的代碼,又不會(huì)在內(nèi)存中留下對(duì)該函數(shù)的引用。

? 結(jié)果就是函數(shù)內(nèi)部的所有變量都會(huì)被立即銷(xiāo)毀——除非將某些變量賦值給了包含作用域(即外

部作用域)中的變量。

閉包還可以用于在對(duì)象中創(chuàng)建私有變量,相關(guān)概念和要點(diǎn)如下。

? 即使 JavaScript 中沒(méi)有正式的私有對(duì)象屬性的概念,但可以使用閉包來(lái)實(shí)現(xiàn)公有方法,而通過(guò)公

有方法可以訪(fǎng)問(wèn)在包含作用域中定義的變量。

? 有權(quán)訪(fǎng)問(wèn)私有變量的公有方法叫做特權(quán)方法。

? 可以使用構(gòu)造函數(shù)模式、原型模式來(lái)實(shí)現(xiàn)自定義類(lèi)型的特權(quán)方法,也可以使用模塊模式、增強(qiáng)

的模塊模式來(lái)實(shí)現(xiàn)單例的特權(quán)方法。

JavaScript 中的函數(shù)表達(dá)式和閉包都是極其有用的特性,利用它們可以實(shí)現(xiàn)很多功能。不過(guò),因?yàn)?/p>

創(chuàng)建閉包必須維護(hù)額外的作用域,所以過(guò)度使用它們可能會(huì)占用大量?jī)?nèi)存。

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

第211頁(yè)

8.1 window 對(duì)象 193

1

2

3

4

5

13

6

7

8

9

10

11

12

BOM

本章內(nèi)容

? 理解 window 對(duì)象——BOM 的核心

? 控制窗口、框架和彈出窗口

? 利用 location 對(duì)象中的頁(yè)面信息

? 使用 navigator 對(duì)象了解瀏覽器

CMAScript 是 JavaScript 的核心,但如果要在 Web 中使用 JavaScript,那么 BOM(瀏覽器對(duì)象模

型)則無(wú)疑才是真正的核心。BOM 提供了很多對(duì)象,用于訪(fǎng)問(wèn)瀏覽器的功能,這些功能與任

何網(wǎng)頁(yè)內(nèi)容無(wú)關(guān)。多年來(lái),缺少事實(shí)上的規(guī)范導(dǎo)致 BOM 既有意思又有問(wèn)題,因?yàn)闉g覽器提供商會(huì)按照各

自的想法隨意去擴(kuò)展它。于是,瀏覽器之間共有的對(duì)象就成為了事實(shí)上的標(biāo)準(zhǔn)。這些對(duì)象在瀏覽器中得以

存在,很大程度上是由于它們提供了與瀏覽器的互操作性。W3C 為了把瀏覽器中 JavaScript 最基本的部分

標(biāo)準(zhǔn)化,已經(jīng)將 BOM 的主要方面納入了 HTML5 的規(guī)范中。

8.1 window 對(duì)象

BOM 的核心對(duì)象是 window,它表示瀏覽器的一個(gè)實(shí)例。在瀏覽器中,window 對(duì)象有雙重角色,

它既是通過(guò) JavaScript 訪(fǎng)問(wèn)瀏覽器窗口的一個(gè)接口,又是 ECMAScript 規(guī)定的 Global 對(duì)象。這意味著

在網(wǎng)頁(yè)中定義的任何一個(gè)對(duì)象、變量和函數(shù),都以 window 作為其 Global 對(duì)象,因此有權(quán)訪(fǎng)問(wèn)

parseInt()等方法。

8.1.1 全局作用域

由于 window 對(duì)象同時(shí)扮演著 ECMAScript 中 Global 對(duì)象的角色,因此所有在全局作用域中聲明

的變量、函數(shù)都會(huì)變成 window 對(duì)象的屬性和方法。來(lái)看下面的例子。

var age = 29;

function sayAge(){

alert(this.age);

}

alert(window.age); //29

sayAge(); //29

window.sayAge(); //29

我們?cè)谌肿饔糜蛑卸x了一個(gè)變量 age 和一個(gè)函數(shù) sayAge(),它們被自動(dòng)歸在了 window 對(duì)象

名下。于是,可以通過(guò) window.age 訪(fǎng)問(wèn)變量 age,可以通過(guò) window.sayAge()訪(fǎng)問(wèn)函數(shù) sayAge()。

E

第 8 章

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

第212頁(yè)

194 第 8 章 BOM

由于 sayAge()存在于全局作用域中,因此 this.age 被映射到 window.age,最終顯示的仍然是正確

的結(jié)果。

拋開(kāi)全局變量會(huì)成為 window 對(duì)象的屬性不談,定義全局變量與在 window 對(duì)象上直接定義屬性還

是有一點(diǎn)差別:全局變量不能通過(guò) delete 操作符刪除,而直接在 window 對(duì)象上的定義的屬性可以。

例如:

var age = 29;

window.color = \"red\";

//在 IE < 9 時(shí)拋出錯(cuò)誤,在其他所有瀏覽器中都返回 false

delete window.age;

//在 IE < 9 時(shí)拋出錯(cuò)誤,在其他所有瀏覽器中都返回 true

delete window.color; //returns true

alert(window.age); //29

alert(window.color); //undefined

DeleteOperatorExample01.htm

剛才使用 var 語(yǔ)句添加的 window 屬性有一個(gè)名為[[Configurable]]的特性,這個(gè)特性的值被

設(shè)置為false,因此這樣定義的屬性不可以通過(guò)delete操作符刪除。IE8及更早版本在遇到使用delete

刪除 window 屬性的語(yǔ)句時(shí),不管該屬性最初是如何創(chuàng)建的,都會(huì)拋出錯(cuò)誤,以示警告。IE9 及更高版

本不會(huì)拋出錯(cuò)誤。

另外,還要記住一件事:嘗試訪(fǎng)問(wèn)未聲明的變量會(huì)拋出錯(cuò)誤,但是通過(guò)查詢(xún) window 對(duì)象,可以知

道某個(gè)可能未聲明的變量是否存在。例如:

//這里會(huì)拋出錯(cuò)誤,因?yàn)?oldValue 未定義

var newValue = oldValue;

//這里不會(huì)拋出錯(cuò)誤,因?yàn)檫@是一次屬性查詢(xún)

//newValue 的值是 undefined

var newValue = window.oldValue;

本章后面將要討論的很多全局 JavaScript 對(duì)象(如 location 和 navigator)實(shí)際上都是 window

對(duì)象的屬性。

Windows Mobile 平臺(tái)的 IE 瀏覽器不允許通過(guò) window.property = value 之類(lèi)

的形式,直接在 window 對(duì)象上創(chuàng)建新的屬性或方法。可是,在全局作用域中聲明的

所有變量和函數(shù),照樣會(huì)變成 window 對(duì)象的成員。

8.1.2 窗口關(guān)系及框架

如果頁(yè)面中包含框架,則每個(gè)框架都擁有自己的 window 對(duì)象,并且保存在 frames 集合中。在 frames

集合中,可以通過(guò)數(shù)值索引(從 0 開(kāi)始,從左至右,從上到下)或者框架名稱(chēng)來(lái)訪(fǎng)問(wèn)相應(yīng)的 window 對(duì)

象。每個(gè) window 對(duì)象都有一個(gè) name 屬性,其中包含框架的名稱(chēng)。下面是一個(gè)包含框架的頁(yè)面:

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

第213頁(yè)

8.1 window 對(duì)象 195

1

2

3

4

5

13

6

7

8

9

10

11

12

<html>

<head>

<title>Frameset Example</title>

</head>

<frameset rows=\"160,*\">

<frame src=\"frame.htm\" name=\"topFrame\">

<frameset cols=\"50%,50%\">

<frame src=\"anotherframe.htm\" name=\"leftFrame\">

<frame src=\"yetanotherframe.htm\" name=\"rightFrame\">

</frameset>

</frameset>

</html>

FramesetExample01.htm

以上代碼創(chuàng)建了一個(gè)框架集,其中一個(gè)框架居上,兩個(gè)框架居下。對(duì)這個(gè)例子而言,可以通過(guò)

window.frames[0]或者 window.frames[\"topFrame\"]來(lái)引用上方的框架。不過(guò),恐怕你最好使用

top 而非 window 來(lái)引用這些框架(例如,通過(guò) top.frames[0])。

我們知道,top 對(duì)象始終指向最高(最外)層的框架,也就是瀏覽器窗口。使用它可以確保在一個(gè)

框架中正確地訪(fǎng)問(wèn)另一個(gè)框架。因?yàn)閷?duì)于在一個(gè)框架中編寫(xiě)的任何代碼來(lái)說(shuō),其中的 window 對(duì)象指向

的都是那個(gè)框架的特定實(shí)例,而非最高層的框架。圖 8-1 展示了在最高層窗口中,通過(guò)代碼來(lái)訪(fǎng)問(wèn)前面

例子中每個(gè)框架的不同方式。

圖 8-1

與 top 相對(duì)的另一個(gè) window 對(duì)象是 parent。顧名思義,parent(父)對(duì)象始終指向當(dāng)前框架的

直接上層框架。在某些情況下,parent 有可能等于 top;但在沒(méi)有框架的情況下,parent 一定等于

top(此時(shí)它們都等于 window)。再看下面的例子。

<html>

<head>

<title>Frameset Example</title>

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

第214頁(yè)

196 第 8 章 BOM

</head>

<frameset rows=\"100,*\">

<frame src=\"frame.htm\" name=\"topFrame\">

<frameset cols=\"50%,50%\">

<frame src=\"anotherframe.htm\" name=\"leftFrame\">

<frame src=\"anotherframeset.htm\" name=\"rightFrame\">

</frameset>

</frameset>

</html>

frameset1.htm

這個(gè)框架集中的一個(gè)框架包含了另一個(gè)框架集,該框架集的代碼如下所示。

<html>

<head>

<title>Frameset Example</title>

</head>

<frameset cols=\"50%,50%\">

<frame src=\"red.htm\" name=\"redFrame\">

<frame src=\"blue.htm\" name=\"blueFrame\">

</frameset>

</html>

anotherframeset.htm

瀏覽器在加載完第一個(gè)框架集以后,會(huì)繼續(xù)將第二個(gè)框架集加載到 rightFrame 中。如果代碼位于

redFrame(或 blueFrame)中,那么 parent 對(duì)象指向的就是 rightFrame??墒?,如果代碼位于

topFrame 中,則 parent 指向的是 top,因?yàn)?topFrame 的直接上層框架就是最外層框架。圖 8-2 展

示了在將前面例子加載到瀏覽器之后,不同 window 對(duì)象的值。

圖 8-2

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

第215頁(yè)

8.1 window 對(duì)象 197

1

2

3

4

5

13

6

7

8

9

10

11

12

注意,除非最高層窗口是通過(guò) window.open()打開(kāi)的(本章后面將會(huì)討論),否則其 window 對(duì)象

的 name 屬性不會(huì)包含任何值。

與框架有關(guān)的最后一個(gè)對(duì)象是 self,它始終指向 window;實(shí)際上,self 和 window 對(duì)象可以互

換使用。引入 self 對(duì)象的目的只是為了與 top 和 parent 對(duì)象對(duì)應(yīng)起來(lái),因此它不格外包含其他值。

所有這些對(duì)象都是 window 對(duì)象的屬性,可以通過(guò) window.parent、window.top 等形式來(lái)訪(fǎng)問(wèn)。

同時(shí),這也意味著可以將不同層次的window 對(duì)象連綴起來(lái),例如window.parent.parent.frames[0]。

在使用框架的情況下,瀏覽器中會(huì)存在多個(gè) Global 對(duì)象。在每個(gè)框架中定義的

全局變量會(huì)自動(dòng)成為框架中 window 對(duì)象的屬性。由于每個(gè) window 對(duì)象都包含原生

類(lèi)型的構(gòu)造函數(shù),因此每個(gè)框架都有一套自己的構(gòu)造函數(shù),這些構(gòu)造函數(shù)一一對(duì)應(yīng),

但并不相等。例如,top.Object 并不等于 top.frames[0].Object。這個(gè)問(wèn)題會(huì)

影響到對(duì)跨框架傳遞的對(duì)象使用 instanceof 操作符。

8.1.3 窗口位置

用來(lái)確定和修改 window 對(duì)象位置的屬性和方法有很多。IE、Safari、Opera 和 Chrome 都提供了

screenLeft 和 screenTop 屬性,分別用于表示窗口相對(duì)于屏幕左邊和上邊的位置。Firefox 則在

screenX 和 screenY 屬性中提供相同的窗口位置信息,Safari 和 Chrome 也同時(shí)支持這兩個(gè)屬性。Opera

雖然也支持 screenX 和 screenY 屬性,但與 screenLeft 和 screenTop 屬性并不對(duì)應(yīng),因此建議大

家不要在 Opera 中使用它們。使用下列代碼可以跨瀏覽器取得窗口左邊和上邊的位置。

var leftPos = (typeof window.screenLeft == \"number\") ?

window.screenLeft : window.screenX;

var topPos = (typeof window.screenTop == \"number\") ?

window.screenTop : window.screenY;

WindowPositionExample01.htm

這個(gè)例子運(yùn)用二元操作符首先確定 screenLeft 和 screenTop 屬性是否存在,如果是(在 IE、

Safari、Opera 和 Chrome 中),則取得這兩個(gè)屬性的值。如果不存在(在 Firefox 中),則取得 screenX

和 screenY 的值。

在使用這些值的過(guò)程中,還必須注意一些小問(wèn)題。在 IE、Opera 中,screenLeft 和 screenTop 中保存

的是從屏幕左邊和上邊到由 window 對(duì)象表示的頁(yè)面可見(jiàn)區(qū)域的距離。換句話(huà)說(shuō),如果 window 對(duì)象是

最外層對(duì)象,而且瀏覽器窗口緊貼屏幕最上端——即 y 軸坐標(biāo)為 0,那么 screenTop 的值就是位于頁(yè)面

可見(jiàn)區(qū)域上方的瀏覽器工具欄的像素高度。但是,在 Chrome、Firefox和 Safari中,screenY 或 screenTop

中保存的是整個(gè)瀏覽器窗口相對(duì)于屏幕的坐標(biāo)值,即在窗口的 y 軸坐標(biāo)為 0 時(shí)返回 0。

更讓人捉摸不透是,F(xiàn)irefox、Safari 和 Chrome 始終返回頁(yè)面中每個(gè)框架的 top.screenX 和

top.screenY 值。即使在頁(yè)面由于被設(shè)置了外邊距而發(fā)生偏移的情況下,相對(duì)于 window 對(duì)象使用

screenX 和 screenY 每次也都會(huì)返回相同的值。而 IE 和 Opera 則會(huì)給出框架相對(duì)于屏幕邊界的精確坐

標(biāo)值。

最終結(jié)果,就是無(wú)法在跨瀏覽器的條件下取得窗口左邊和上邊的精確坐標(biāo)值。然而,使用 moveTo()

和 moveBy()方法倒是有可能將窗口精確地移動(dòng)到一個(gè)新位置。這兩個(gè)方法都接收兩個(gè)參數(shù),其中

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

第216頁(yè)

198 第 8 章 BOM

moveTo()接收的是新位置的 x 和 y 坐標(biāo)值,而 moveBy()接收的是在水平和垂直方向上移動(dòng)的像素?cái)?shù)。

下面來(lái)看幾個(gè)例子:

//將窗口移動(dòng)到屏幕左上角

window.moveTo(0,0);

//將窗向下移動(dòng) 100 像素

window.moveBy(0,100);

//將窗口移動(dòng)到(200,300)

window.moveTo(200,300);

//將窗口向左移動(dòng) 50 像素

window.moveBy(-50,0);

需要注意的是,這兩個(gè)方法可能會(huì)被瀏覽器禁用;而且,在 Opera 和 IE 7(及更高版本)中默認(rèn)就

是禁用的。另外,這兩個(gè)方法都不適用于框架,只能對(duì)最外層的 window 對(duì)象使用。

8.1.4 窗口大小

跨瀏覽器確定一個(gè)窗口的大小不是一件簡(jiǎn)單的事。IE9+、Firefox、Safari、Opera 和 Chrome 均為此提

供了 4 個(gè)屬性:innerWidth、innerHeight、outerWidth 和 outerHeight。在 IE9+、Safari 和 Firefox

中,outerWidth 和 outerHeight 返回瀏覽器窗口本身的尺寸(無(wú)論是從最外層的 window 對(duì)象還是從

某個(gè)框架訪(fǎng)問(wèn))。在 Opera中,這兩個(gè)屬性的值表示頁(yè)面視圖容器①的大小。而 innerWidth 和 innerHeight

則表示該容器中頁(yè)面視圖區(qū)的大小(減去邊框?qū)挾龋?。?Chrome 中,outerWidth、outerHeight 與

innerWidth、innerHeight 返回相同的值,即視口(viewport)大小而非瀏覽器窗口大小。

IE8 及更早版本沒(méi)有提供取得當(dāng)前瀏覽器窗口尺寸的屬性;不過(guò),它通過(guò) DOM 提供了頁(yè)面可見(jiàn)區(qū)域

的相關(guān)信息。

在 IE、Firefox、Safari、Opera 和 Chrome 中,document.documentElement.clientWidth 和

document.documentElement.clientHeight 中保存了頁(yè)面視口的信息。在 IE6 中,這些屬性必須在

標(biāo)準(zhǔn)模式下才有效;如果是混雜模式,就必須通過(guò) document.body.clientWidth 和 document.body.

clientHeight 取得相同信息。而對(duì)于混雜模式下的 Chrome,則無(wú)論通過(guò) document.documentElement 還是 document.body 中的 clientWidth 和 clientHeight 屬性,都可以取得視口的大小。

雖然最終無(wú)法確定瀏覽器窗口本身的大小,但卻可以取得頁(yè)面視口的大小,如下所示。

var pageWidth = window.innerWidth,

pageHeight = window.innerHeight;

if (typeof pageWidth != \"number\"){

if (document.compatMode == \"CSS1Compat\"){

pageWidth = document.documentElement.clientWidth;

pageHeight = document.documentElement.clientHeight;

} else {

pageWidth = document.body.clientWidth;

pageHeight = document.body.clientHeight;

}

}

WindowSizeExample01.htm

——————————

① 這里所謂的“頁(yè)面視圖容器”指的是 Opera 中單個(gè)標(biāo)簽頁(yè)對(duì)應(yīng)的瀏覽器窗口。

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

第217頁(yè)

8.1 window 對(duì)象 199

1

2

3

4

5

13

6

7

8

9

10

11

12

在以上代碼中,我們首先將 window.innerWidth 和 window.innerHeight 的值分別賦給了

pageWidth 和 pageHeight。然后檢查 pageWidth 中保存的是不是一個(gè)數(shù)值;如果不是,則通過(guò)檢查

document.compatMode(這個(gè)屬性將在第 10 章全面討論)來(lái)確定頁(yè)面是否處于標(biāo)準(zhǔn)模式。如果是,則

分別使用 document.documentElement.clientWidth 和 document.documentElement.clientHeight 的值。否則,就使用 document.body.clientWidth 和 document.body.clientHeight 的值。

對(duì)于移動(dòng)設(shè)備,window.innerWidth 和 window.innerHeight 保存著可見(jiàn)視口,也就是屏幕上可

見(jiàn)頁(yè)面區(qū)域的大小。移動(dòng) IE 瀏覽器不支持這些屬性,但通過(guò) document.documentElement.clientWidth 和 document.documentElement.clientHeihgt 提供了相同的信息。隨著頁(yè)面的縮放,這些值

也會(huì)相應(yīng)變化。

在其他移動(dòng)瀏覽器中,document.documentElement 度量的是布局視口,即渲染后頁(yè)面的實(shí)際大

?。ㄅc可見(jiàn)視口不同,可見(jiàn)視口只是整個(gè)頁(yè)面中的一小部分)。移動(dòng) IE 瀏覽器把布局視口的信息保存在

document.body.clientWidth和document.body.clientHeight中。這些值不會(huì)隨著頁(yè)面縮放變化。

由于與桌面瀏覽器間存在這些差異,最好是先檢測(cè)一下用戶(hù)是否在使用移動(dòng)設(shè)備,然后再?zèng)Q定使用

哪個(gè)屬性。

有關(guān)移動(dòng)設(shè)備視口的話(huà)題比較復(fù)雜,有很多非常規(guī)的情形,也有各種各樣的建議。

移動(dòng)開(kāi)發(fā)咨詢(xún)師 Peter-Paul Koch 記述了他對(duì)這個(gè)問(wèn)題的研究:http://t.cn/zOZs0Tz。如

果你在做移動(dòng) Web 開(kāi)發(fā),推薦你讀一讀這篇文章。

另外,使用 resizeTo()和 resizeBy()方法可以調(diào)整瀏覽器窗口的大小。這兩個(gè)方法都接收兩個(gè)

參數(shù),其中 resizeTo()接收瀏覽器窗口的新寬度和新高度,而 resizeBy()接收新窗口與原窗口的寬

度和高度之差。來(lái)看下面的例子。

//調(diào)整到 100×100

window.resizeTo(100, 100);

//調(diào)整到 200×150

window.resizeBy(100, 50);

//調(diào)整到 300×300

window.resizeTo(300, 300);

需要注意的是,這兩個(gè)方法與移動(dòng)窗口位置的方法類(lèi)似,也有可能被瀏覽器禁用;而且,在 Opera

和 IE7(及更高版本)中默認(rèn)就是禁用的。另外,這兩個(gè)方法同樣不適用于框架,而只能對(duì)最外層的

window 對(duì)象使用。

8.1.5 導(dǎo)航和打開(kāi)窗口

使用 window.open()方法既可以導(dǎo)航到一個(gè)特定的 URL,也可以打開(kāi)一個(gè)新的瀏覽器窗口。這個(gè)

方法可以接收 4 個(gè)參數(shù):要加載的 URL、窗口目標(biāo)、一個(gè)特性字符串以及一個(gè)表示新頁(yè)面是否取代瀏覽

器歷史記錄中當(dāng)前加載頁(yè)面的布爾值。通常只須傳遞第一個(gè)參數(shù),最后一個(gè)參數(shù)只在不打開(kāi)新窗口的情

況下使用。

如果為 window.open()傳遞了第二個(gè)參數(shù),而且該參數(shù)是已有窗口或框架的名稱(chēng),那么就會(huì)在具

有該名稱(chēng)的窗口或框架中加載第一個(gè)參數(shù)指定的 URL。看下面的例子。

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

第218頁(yè)

200 第 8 章 BOM

//等同于< a href=\"http://www.wrox.com\" target=\"topFrame\"></a>

window.open(\"http://www.wrox.com/\", \"topFrame\");

調(diào)用這行代碼,就如同用戶(hù)單擊了 href 屬性為 http://www.wrox.com/,target 屬性為\"topFrame\"

的鏈接。如果有一個(gè)名叫\"topFrame\"的窗口或者框架,就會(huì)在該窗口或框架加載這個(gè) URL;否則,就

會(huì)創(chuàng)建一個(gè)新窗口并將其命名為\"topFrame\"。此外,第二個(gè)參數(shù)也可以是下列任何一個(gè)特殊的窗口名

稱(chēng):_self、_parent、_top 或_blank。

1. 彈出窗口

如果給 window.open()傳遞的第二個(gè)參數(shù)并不是一個(gè)已經(jīng)存在的窗口或框架,那么該方法就會(huì)根

據(jù)在第三個(gè)參數(shù)位置上傳入的字符串創(chuàng)建一個(gè)新窗口或新標(biāo)簽頁(yè)。如果沒(méi)有傳入第三個(gè)參數(shù),那么就會(huì)

打開(kāi)一個(gè)帶有全部默認(rèn)設(shè)置(工具欄、地址欄和狀態(tài)欄等)的新瀏覽器窗口(或者打開(kāi)一個(gè)新標(biāo)簽頁(yè)—

—根據(jù)瀏覽器設(shè)置)。在不打開(kāi)新窗口的情況下,會(huì)忽略第三個(gè)參數(shù)。

第三個(gè)參數(shù)是一個(gè)逗號(hào)分隔的設(shè)置字符串,表示在新窗口中都顯示哪些特性。下表列出了可以出現(xiàn)

在這個(gè)字符串中的設(shè)置選項(xiàng)。

設(shè) 置 值 說(shuō) 明

fullscreen yes或no 表示瀏覽器窗口是否最大化。僅限IE

height 數(shù)值 表示新窗口的高度。不能小于100

left 數(shù)值 表示新窗口的左坐標(biāo)。不能是負(fù)值

location yes或no 表示是否在瀏覽器窗口中顯示地址欄。不同瀏覽器的默認(rèn)值不同。如果

設(shè)置為no,地址欄可能會(huì)隱藏,也可能會(huì)被禁用(取決于瀏覽器)

menubar yes或no 表示是否在瀏覽器窗口中顯示菜單欄。默認(rèn)值為no

resizable yes或no 表示是否可以通過(guò)拖動(dòng)瀏覽器窗口的邊框改變其大小。默認(rèn)值為no

scrollbars yes或no 表示如果內(nèi)容在視口中顯示不下,是否允許滾動(dòng)。默認(rèn)值為no

status yes或no 表示是否在瀏覽器窗口中顯示狀態(tài)欄。默認(rèn)值為no

toolbar yes或no 表示是否在瀏覽器窗口中顯示工具欄。默認(rèn)值為no

top 數(shù)值 表示新窗口的上坐標(biāo)。不能是負(fù)值

width 數(shù)值 表示新窗口的寬度。不能小于100

表中所列的部分或全部設(shè)置選項(xiàng),都可以通過(guò)逗號(hào)分隔的名值對(duì)列表來(lái)指定。其中,名值對(duì)以等號(hào)

表示(注意,整個(gè)特性字符串中不允許出現(xiàn)空格),如下面的例子所示。

window.open(\"http://www.wrox.com/\",\"wroxWindow\",

\"height=400,width=400,top=10,left=10,resizable=yes\");

這行代碼會(huì)打開(kāi)一個(gè)新的可以調(diào)整大小的窗口,窗口初始大小為 400×400 像素,并且距屏幕上沿

和左邊各 10 像素。

window.open()方法會(huì)返回一個(gè)指向新窗口的引用。引用的對(duì)象與其他 window 對(duì)象大致相似,但

我們可以對(duì)其進(jìn)行更多控制。例如,有些瀏覽器在默認(rèn)情況下可能不允許我們針對(duì)主瀏覽器窗口調(diào)整大

小或移動(dòng)位置,但卻允許我們針對(duì)通過(guò) window.open()創(chuàng)建的窗口調(diào)整大小或移動(dòng)位置。通過(guò)這個(gè)返

回的對(duì)象,可以像操作其他窗口一樣操作新打開(kāi)的窗口,如下所示。

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

第219頁(yè)

8.1 window 對(duì)象 201

1

2

3

4

5

13

6

7

8

9

10

11

12

var wroxWin = window.open(\"http://www.wrox.com/\",\"wroxWindow\",

\"height=400,width=400,top=10,left=10,resizable=yes\");

//調(diào)整大小

wroxWin.resizeTo(500,500);

//移動(dòng)位置

wroxWin.moveTo(100,100);

調(diào)用 close()方法還可以關(guān)閉新打開(kāi)的窗口。

wroxWin.close();

但是,這個(gè)方法僅適用于通過(guò) window.open()打開(kāi)的彈出窗口。對(duì)于瀏覽器的主窗口,如果沒(méi)有

得到用戶(hù)的允許是不能關(guān)閉它的。不過(guò),彈出窗口倒是可以調(diào)用 top.close()在不經(jīng)用戶(hù)允許的情況

下關(guān)閉自己。彈出窗口關(guān)閉之后,窗口的引用仍然還在,但除了像下面這樣檢測(cè)其 closed 屬性之外,

已經(jīng)沒(méi)有其他用處了。

wroxWin.close();

alert(wroxWin.closed); //true

新創(chuàng)建的 window 對(duì)象有一個(gè) opener 屬性,其中保存著打開(kāi)它的原始窗口對(duì)象。這個(gè)屬性只在彈出

窗口中的最外層 window 對(duì)象(top)中有定義,而且指向調(diào)用 window.open()的窗口或框架。例如:

var wroxWin = window.open(\"http://www.wrox.com/\",\"wroxWindow\",

\"height=400,width=400,top=10,left=10,resizable=yes\");

alert(wroxWin.opener == window); //true

雖然彈出窗口中有一個(gè)指針指向打開(kāi)它的原始窗口,但原始窗口中并沒(méi)有這樣的指針指向彈出窗

口。窗口并不跟蹤記錄它們打開(kāi)的彈出窗口,因此我們只能在必要的時(shí)候自己來(lái)手動(dòng)實(shí)現(xiàn)跟蹤。

有些瀏覽器(如 IE8 和 Chrome)會(huì)在獨(dú)立的進(jìn)程中運(yùn)行每個(gè)標(biāo)簽頁(yè)。當(dāng)一個(gè)標(biāo)簽頁(yè)打開(kāi)另一個(gè)標(biāo)

簽頁(yè)時(shí),如果兩個(gè) window 對(duì)象之間需要彼此通信,那么新標(biāo)簽頁(yè)就不能運(yùn)行在獨(dú)立的進(jìn)程中。在 Chrome

中,將新創(chuàng)建的標(biāo)簽頁(yè)的 opener 屬性設(shè)置為 null,即表示在單獨(dú)的進(jìn)程中運(yùn)行新標(biāo)簽頁(yè),如下所示。

var wroxWin = window.open(\"http://www.wrox.com/\",\"wroxWindow\",

\"height=400,width=400,top=10,left=10,resizable=yes\");

wroxWin.opener = null;

將 opener 屬性設(shè)置為 null 就是告訴瀏覽器新創(chuàng)建的標(biāo)簽頁(yè)不需要與打開(kāi)它的標(biāo)簽頁(yè)通信,因此

可以在獨(dú)立的進(jìn)程中運(yùn)行。標(biāo)簽頁(yè)之間的聯(lián)系一旦切斷,將沒(méi)有辦法恢復(fù)。

2. 安全限制

曾經(jīng)有一段時(shí)間,廣告商在網(wǎng)上使用彈出窗口達(dá)到了肆無(wú)忌憚的程度。他們經(jīng)常把彈出窗口打扮成

系統(tǒng)對(duì)話(huà)框的模樣,引誘用戶(hù)去點(diǎn)擊其中的廣告。由于看起來(lái)像是系統(tǒng)對(duì)話(huà)框,一般用戶(hù)很難分辨是真

是假。為了解決這個(gè)問(wèn)題,有些瀏覽器開(kāi)始在彈出窗口配置方面增加限制。

Windows XP SP2 中的 IE6 對(duì)彈出窗口施加了多方面的安全限制,包括不允許在屏幕之外創(chuàng)建彈出窗

口、不允許將彈出窗口移動(dòng)到屏幕以外、不允許關(guān)閉狀態(tài)欄等。IE7 則增加了更多的安全限制,如不允

許關(guān)閉地址欄、默認(rèn)情況下不允許移動(dòng)彈出窗口或調(diào)整其大小。Firefox 1 從一開(kāi)始就不支持修改狀態(tài)欄,

因此無(wú)論給 window.open()傳入什么樣的特性字符串,彈出窗口中都會(huì)無(wú)一例外地顯示狀態(tài)欄。后來(lái)

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

第220頁(yè)

202 第 8 章 BOM

的 Firefox 3 又強(qiáng)制始終在彈出窗口中顯示地址欄。Opera 只會(huì)在主瀏覽器窗口中打開(kāi)彈出窗口,但不允

許它們出現(xiàn)在可能與系統(tǒng)對(duì)話(huà)框混淆的地方。

此外,有的瀏覽器只根據(jù)用戶(hù)操作來(lái)創(chuàng)建彈出窗口。這樣一來(lái),在頁(yè)面尚未加載完成時(shí)調(diào)用

window.open()的語(yǔ)句根本不會(huì)執(zhí)行,而且還可能會(huì)將錯(cuò)誤消息顯示給用戶(hù)。換句話(huà)說(shuō),只能通過(guò)單

擊或者擊鍵來(lái)打開(kāi)彈出窗口。

對(duì)于那些不是用戶(hù)有意打開(kāi)的彈出窗口,Chrome 采取了不同的處理方式。它不會(huì)像其他瀏覽器那

樣簡(jiǎn)單地屏蔽這些彈出窗口,而是只顯示它們的標(biāo)題欄,并把它們放在瀏覽器窗口的右下角。

在打開(kāi)計(jì)算機(jī)硬盤(pán)中的網(wǎng)頁(yè)時(shí),IE 會(huì)解除對(duì)彈出窗口的某些限制。但是在服務(wù)器

上執(zhí)行這些代碼會(huì)受到對(duì)彈出窗口的限制。

3. 彈出窗口屏蔽程序

大多數(shù)瀏覽器都內(nèi)置有彈出窗口屏蔽程序,而沒(méi)有內(nèi)置此類(lèi)程序的瀏覽器,也可以安裝 Yahoo!

Toolbar 等帶有內(nèi)置屏蔽程序的實(shí)用工具。結(jié)果就是用戶(hù)可以將絕大多數(shù)不想看到彈出窗口屏蔽掉。于

是,在彈出窗口被屏蔽時(shí),就應(yīng)該考慮兩種可能性。如果是瀏覽器內(nèi)置的屏蔽程序阻止的彈出窗口,那

么 window.open()很可能會(huì)返回 null。此時(shí),只要檢測(cè)這個(gè)返回的值就可以確定彈出窗口是否被屏蔽

了,如下面的例子所示。

var wroxWin = window.open(\"http://www.wrox.com\", \"_blank\");

if (wroxWin == null){

alert(\"The popup was blocked!\");

}

如果是瀏覽器擴(kuò)展或其他程序阻止的彈出窗口,那么 window.open()通常會(huì)拋出一個(gè)錯(cuò)誤。因此,

要想準(zhǔn)確地檢測(cè)出彈出窗口是否被屏蔽,必須在檢測(cè)返回值的同時(shí),將對(duì) window.open()的調(diào)用封裝

在一個(gè) try-catch 塊中,如下所示。

var blocked = false;

try {

var wroxWin = window.open(\"http://www.wrox.com\", \"_blank\");

if (wroxWin == null){

blocked = true;

}

} catch (ex){

blocked = true;

}

if (blocked){

alert(\"The popup was blocked!\");

}

PopupBlockerExample01.htm

在任何情況下,以上代碼都可以檢測(cè)出調(diào)用 window.open()打開(kāi)的彈出窗口是不是被屏蔽了。但

要注意的是,檢測(cè)彈出窗口是否被屏蔽只是一方面,它并不會(huì)阻止瀏覽器顯示與被屏蔽的彈出窗口有關(guān)

的消息。

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

第221頁(yè)

8.1 window 對(duì)象 203

1

2

3

4

5

13

6

7

8

9

10

11

12

8.1.6 間歇調(diào)用和超時(shí)調(diào)用

JavaScript 是單線(xiàn)程語(yǔ)言,但它允許通過(guò)設(shè)置超時(shí)值和間歇時(shí)間值來(lái)調(diào)度代碼在特定的時(shí)刻執(zhí)行。

前者是在指定的時(shí)間過(guò)后執(zhí)行代碼,而后者則是每隔指定的時(shí)間就執(zhí)行一次代碼。

超時(shí)調(diào)用需要使用 window 對(duì)象的 setTimeout()方法,它接受兩個(gè)參數(shù):要執(zhí)行的代碼和以毫秒

表示的時(shí)間(即在執(zhí)行代碼前需要等待多少毫秒)。其中,第一個(gè)參數(shù)可以是一個(gè)包含 JavaScript 代碼的

字符串(就和在 eval()函數(shù)中使用的字符串一樣),也可以是一個(gè)函數(shù)。例如,下面對(duì) setTimeout()

的兩次調(diào)用都會(huì)在一秒鐘后顯示一個(gè)警告框。

//不建議傳遞字符串!

setTimeout(\"alert('Hello world!') \", 1000);

//推薦的調(diào)用方式

setTimeout(function() {

alert(\"Hello world!\");

}, 1000);

TimeoutExample01.htm

雖然這兩種調(diào)用方式都沒(méi)有問(wèn)題,但由于傳遞字符串可能導(dǎo)致性能損失,因此不建議以字符串作為

第一個(gè)參數(shù)。

第二個(gè)參數(shù)是一個(gè)表示等待多長(zhǎng)時(shí)間的毫秒數(shù),但經(jīng)過(guò)該時(shí)間后指定的代碼不一定會(huì)執(zhí)行。

JavaScript 是一個(gè)單線(xiàn)程序的解釋器,因此一定時(shí)間內(nèi)只能執(zhí)行一段代碼。為了控制要執(zhí)行的代碼,就

有一個(gè) JavaScript 任務(wù)隊(duì)列。這些任務(wù)會(huì)按照將它們添加到隊(duì)列的順序執(zhí)行。setTimeout()的第二個(gè)

參數(shù)告訴 JavaScript 再過(guò)多長(zhǎng)時(shí)間把當(dāng)前任務(wù)添加到隊(duì)列中。如果隊(duì)列是空的,那么添加的代碼會(huì)立即

執(zhí)行;如果隊(duì)列不是空的,那么它就要等前面的代碼執(zhí)行完了以后再執(zhí)行。

調(diào)用 setTimeout()之后,該方法會(huì)返回一個(gè)數(shù)值 ID,表示超時(shí)調(diào)用。這個(gè)超時(shí)調(diào)用 ID 是計(jì)劃執(zhí)

行代碼的唯一標(biāo)識(shí)符,可以通過(guò)它來(lái)取消超時(shí)調(diào)用。要取消尚未執(zhí)行的超時(shí)調(diào)用計(jì)劃,可以調(diào)用

clearTimeout()方法并將相應(yīng)的超時(shí)調(diào)用 ID 作為參數(shù)傳遞給它,如下所示。

//設(shè)置超時(shí)調(diào)用

var timeoutId = setTimeout(function() {

alert(\"Hello world!\");

}, 1000);

//注意:把它取消

clearTimeout(timeoutId);

TimeoutExample02.htm

只要是在指定的時(shí)間尚未過(guò)去之前調(diào)用 clearTimeout(),就可以完全取消超時(shí)調(diào)用。前面的代碼

在設(shè)置超時(shí)調(diào)用之后馬上又調(diào)用了 clearTimeout(),結(jié)果就跟什么也沒(méi)有發(fā)生一樣。

超時(shí)調(diào)用的代碼都是在全局作用域中執(zhí)行的,因此函數(shù)中 this 的值在非嚴(yán)格模

式下指向 window 對(duì)象,在嚴(yán)格模式下是 undefined。

間歇調(diào)用與超時(shí)調(diào)用類(lèi)似,只不過(guò)它會(huì)按照指定的時(shí)間間隔重復(fù)執(zhí)行代碼,直至間歇調(diào)用被取消或

者頁(yè)面被卸載。設(shè)置間歇調(diào)用的方法是 setInterval(),它接受的參數(shù)與 setTimeout()相同:要執(zhí)

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

第222頁(yè)

204 第 8 章 BOM

行的代碼(字符串或函數(shù))和每次執(zhí)行之前需要等待的毫秒數(shù)。下面來(lái)看一個(gè)例子。

//不建議傳遞字符串!

setInterval (\"alert('Hello world!') \", 10000);

//推薦的調(diào)用方式

setInterval (function() {

alert(\"Hello world!\");

}, 10000);

IntervalExample01.htm

調(diào)用 setInterval()方法同樣也會(huì)返回一個(gè)間歇調(diào)用 ID,該 ID 可用于在將來(lái)某個(gè)時(shí)刻取消間歇

調(diào)用。要取消尚未執(zhí)行的間歇調(diào)用,可以使用 clearInterval()方法并傳入相應(yīng)的間歇調(diào)用 ID。取消

間歇調(diào)用的重要性要遠(yuǎn)遠(yuǎn)高于取消超時(shí)調(diào)用,因?yàn)樵诓患痈缮娴那闆r下,間歇調(diào)用將會(huì)一直執(zhí)行到頁(yè)面

卸載。以下是一個(gè)常見(jiàn)的使用間歇調(diào)用的例子。

var num = 0;

var max = 10;

var intervalId = null;

function incrementNumber() {

num++;

//如果執(zhí)行次數(shù)達(dá)到了 max 設(shè)定的值,則取消后續(xù)尚未執(zhí)行的調(diào)用

if (num == max) {

clearInterval(intervalId);

alert(\"Done\");

}

}

intervalId = setInterval(incrementNumber, 500);

IntervalExample02.htm

在這個(gè)例子中,變量 num 每半秒鐘遞增一次,當(dāng)遞增到最大值時(shí)就會(huì)取消先前設(shè)定的間歇調(diào)用。這

個(gè)模式也可以使用超時(shí)調(diào)用來(lái)實(shí)現(xiàn),如下所示。

var num = 0;

var max = 10;

function incrementNumber() {

num++;

//如果執(zhí)行次數(shù)未達(dá)到 max 設(shè)定的值,則設(shè)置另一次超時(shí)調(diào)用

if (num < max) {

setTimeout(incrementNumber, 500);

} else {

alert(\"Done\");

}

}

setTimeout(incrementNumber, 500);

TimeoutExample03.htm

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

第223頁(yè)

8.1 window 對(duì)象 205

1

2

3

4

5

13

6

7

8

9

10

11

12

可見(jiàn),在使用超時(shí)調(diào)用時(shí),沒(méi)有必要跟蹤超時(shí)調(diào)用 ID,因?yàn)槊看螆?zhí)行代碼之后,如果不再設(shè)置另

一次超時(shí)調(diào)用,調(diào)用就會(huì)自行停止。一般認(rèn)為,使用超時(shí)調(diào)用來(lái)模擬間歇調(diào)用的是一種最佳模式。在開(kāi)

發(fā)環(huán)境下,很少使用真正的間歇調(diào)用,原因是后一個(gè)間歇調(diào)用可能會(huì)在前一個(gè)間歇調(diào)用結(jié)束之前啟動(dòng)。

而像前面示例中那樣使用超時(shí)調(diào)用,則完全可以避免這一點(diǎn)。所以,最好不要使用間歇調(diào)用。

8.1.7 系統(tǒng)對(duì)話(huà)框

瀏覽器通過(guò) alert()、confirm()和 prompt()方法可以調(diào)用系統(tǒng)對(duì)話(huà)框向用戶(hù)顯示消息。系統(tǒng)對(duì)

話(huà)框與在瀏覽器中顯示的網(wǎng)頁(yè)沒(méi)有關(guān)系,也不包含 HTML。它們的外觀由操作系統(tǒng)及(或)瀏覽器設(shè)置

決定,而不是由 CSS 決定。此外,通過(guò)這幾個(gè)方法打開(kāi)的對(duì)話(huà)框都是同步和模態(tài)的。也就是說(shuō),顯示這

些對(duì)話(huà)框的時(shí)候代碼會(huì)停止執(zhí)行,而關(guān)掉這些對(duì)話(huà)框后代碼又會(huì)恢復(fù)執(zhí)行。

本書(shū)各章經(jīng)常會(huì)用到 alert()方法,這個(gè)方法接受一個(gè)字符串并將其顯示給用戶(hù)。具體來(lái)說(shuō),調(diào)用

alert()方法的結(jié)果就是向用戶(hù)顯示一個(gè)系統(tǒng)對(duì)話(huà)框,其中包含指定的文本和一個(gè) OK(“確定”)按鈕。

例如,alert(\"Hello world!\")會(huì)在 Windows XP 系統(tǒng)的 IE 中生成如圖 8-3 所示的對(duì)話(huà)框。

通常使用 alert()生成的“警告”對(duì)話(huà)框向用戶(hù)顯示一些他們無(wú)法控制的消息,例如錯(cuò)誤消息。而

用戶(hù)只能在看完消息后關(guān)閉對(duì)話(huà)框。

第二種對(duì)話(huà)框是調(diào)用 confirm()方法生成的。從向用戶(hù)顯示消息的方面來(lái)看,這種“確認(rèn)”對(duì)話(huà)

框很像是一個(gè)“警告”對(duì)話(huà)框。但二者的主要區(qū)別在于“確認(rèn)”對(duì)話(huà)框除了顯示 OK 按鈕外,還會(huì)顯示

一個(gè) Cancel(“取消”)按鈕,兩個(gè)按鈕可以讓用戶(hù)決定是否執(zhí)行給定的操作。例如,confirm(\"Are you

sure?\")會(huì)顯示如圖 8-4 所示的確認(rèn)對(duì)話(huà)框。

圖 8-3 圖 8-4

為了確定用戶(hù)是單擊了 OK 還是 Cancel,可以檢查 confirm()方法返回的布爾值:true 表示單擊

了 OK,false 表示單擊了 Cancel 或單擊了右上角的 X 按鈕。確認(rèn)對(duì)話(huà)框的典型用法如下。

if (confirm(\"Are you sure?\")) {

alert(\"I'm so glad you're sure! \");

} else {

alert(\"I'm sorry to hear you're not sure. \");

}

在這個(gè)例子中,第一行代碼(if 條件語(yǔ)句)會(huì)向用戶(hù)顯示一個(gè)確認(rèn)對(duì)話(huà)框。如果用戶(hù)單擊了 OK,

則通過(guò)一個(gè)警告框向用戶(hù)顯示消息 I’m so glad you’ re sure! 。如果用戶(hù)單擊的是 Cancel 按鈕,則通過(guò)警

告框顯示 I’m sorry to hear you’re not sure.。這種模式經(jīng)常在用戶(hù)想要執(zhí)行刪除操作的時(shí)候使用,例如刪

除電子郵件。

最后一種對(duì)話(huà)框是通過(guò)調(diào)用 prompt()方法生成的,這是一個(gè)“提示”框,用于提示用戶(hù)輸入一些

文本。提示框中除了顯示 OK 和 Cancel 按鈕之外,還會(huì)顯示一個(gè)文本輸入域,以供用戶(hù)在其中輸入內(nèi)容。

prompt()方法接受兩個(gè)參數(shù):要顯示給用戶(hù)的文本提示和文本輸入域的默認(rèn)值(可以是一個(gè)空字符串)。

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

第224頁(yè)

206 第 8 章 BOM

調(diào)用 prompt(\"What's your name?\",\"Michael\")會(huì)得到如圖 8-5 所示的對(duì)話(huà)框。

圖 8-5

如果用戶(hù)單擊了 OK 按鈕,則 prompt()返回文本輸入域的值;如果用戶(hù)單擊了 Cancel 或沒(méi)有單擊

OK 而是通過(guò)其他方式關(guān)閉了對(duì)話(huà)框,則該方法返回 null。下面是一個(gè)例子。

var result = prompt(\"What is your name? \", \"\");

if (result !== null) {

alert(\"Welcome, \" + result);

}

綜上所述,這些系統(tǒng)對(duì)話(huà)框很適合向用戶(hù)顯示消息并請(qǐng)用戶(hù)作出決定。由于不涉及 HTML、CSS 或

JavaScript,因此它們是增強(qiáng) Web 應(yīng)用程序的一種便捷方式。

除了上述三種對(duì)話(huà)框之外,Google Chrome 瀏覽器還引入了一種新特性。如果當(dāng)前腳本在執(zhí)行過(guò)程

中會(huì)打開(kāi)兩個(gè)或多個(gè)對(duì)話(huà)框,那么從第二個(gè)對(duì)話(huà)框開(kāi)始,每個(gè)對(duì)話(huà)框中都會(huì)顯示一個(gè)復(fù)選框,以便用戶(hù)

阻止后續(xù)的對(duì)話(huà)框顯示,除非用戶(hù)刷新頁(yè)面(見(jiàn)圖 8-6)。

圖 8-6

如果用戶(hù)勾選了其中的復(fù)選框,并且關(guān)閉了對(duì)話(huà)框,那么除非用戶(hù)刷新頁(yè)面,所有后續(xù)的系統(tǒng)對(duì)話(huà)

框(包括警告框、確認(rèn)框和提示框)都會(huì)被屏蔽。Chrome 沒(méi)有就對(duì)話(huà)框是否顯示向開(kāi)發(fā)人員提供任何

信息。由于瀏覽器會(huì)在空閑時(shí)重置對(duì)話(huà)框計(jì)數(shù)器,因此如果兩次獨(dú)立的用戶(hù)操作分別打開(kāi)兩個(gè)警告框,

那么這兩個(gè)警告框中都不會(huì)顯示復(fù)選框。而如果是同一次用戶(hù)操作會(huì)生成兩個(gè)警告框,那么第二個(gè)警告

框中就會(huì)顯示復(fù)選框。這個(gè)新特性出現(xiàn)以后,IE9 和 Firefox 4 也實(shí)現(xiàn)了它。

還有兩個(gè)可以通過(guò) JavaScript 打開(kāi)的對(duì)話(huà)框,即“查找”和“打印”。這兩個(gè)對(duì)話(huà)框都是異步顯示

的,能夠?qū)⒖刂茩?quán)立即交還給腳本。這兩個(gè)對(duì)話(huà)框與用戶(hù)通過(guò)瀏覽器菜單的“查找”和“打印”命令

打開(kāi)的對(duì)話(huà)框相同。而在 JavaScript 中則可以像下面這樣通過(guò) window 對(duì)象的 find()和 print()方法

打開(kāi)它們。

//顯示“打印”對(duì)話(huà)框

window.print();

//顯示“查找”對(duì)話(huà)框

window.find();

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

第225頁(yè)

8.2 location 對(duì)象 207

1

2

3

4

5

13

6

7

8

9

10

11

12

這兩個(gè)方法同樣不會(huì)就用戶(hù)在對(duì)話(huà)框中的操作給出任何信息,因此它們的用處有限。另外,既然這

兩個(gè)對(duì)話(huà)框是異步顯示的,那么 Chrome 的對(duì)話(huà)框計(jì)數(shù)器就不會(huì)將它們計(jì)算在內(nèi),所以它們也不會(huì)受用

戶(hù)禁用后續(xù)對(duì)話(huà)框顯示的影響。

8.2 location 對(duì)象

location 是最有用的 BOM 對(duì)象之一,它提供了與當(dāng)前窗口中加載的文檔有關(guān)的信息,還提供了一

些導(dǎo)航功能。事實(shí)上,location 對(duì)象是很特別的一個(gè)對(duì)象,因?yàn)樗仁?window 對(duì)象的屬性,也是

document 對(duì)象的屬性;換句話(huà)說(shuō),window.location 和 document.location 引用的是同一個(gè)對(duì)象。

location 對(duì)象的用處不只表現(xiàn)在它保存著當(dāng)前文檔的信息,還表現(xiàn)在它將 URL 解析為獨(dú)立的片段,讓

開(kāi)發(fā)人員可以通過(guò)不同的屬性訪(fǎng)問(wèn)這些片段。下表列出了 location 對(duì)象的所有屬性(注:省略了每個(gè)屬

性前面的 location 前綴)。

屬 性 名 例 子 說(shuō) 明

hash \"#contents\" 返回URL中的hash(#號(hào)后跟零或多個(gè)字符),如果URL

中不包含散列,則返回空字符串

host \"www.wrox.com:80\" 返回服務(wù)器名稱(chēng)和端口號(hào)(如果有)

hostname \"www.wrox.com\" 返回不帶端口號(hào)的服務(wù)器名稱(chēng)

href \"http:/www.wrox.com\" 返回當(dāng)前加載頁(yè)面的完整URL。而location對(duì)象的

toString()方法也返回這個(gè)值

pathname \"/WileyCDA/\" 返回URL中的目錄和(或)文件名

port \"8080\" 返回URL中指定的端口號(hào)。如果URL中不包含端口號(hào),則

這個(gè)屬性返回空字符串

protocol \"http:\" 返回頁(yè)面使用的協(xié)議。通常是http:或https:

search \"?q=javascript\" 返回URL的查詢(xún)字符串。這個(gè)字符串以問(wèn)號(hào)開(kāi)頭

8.2.1 查詢(xún)字符串參數(shù)

雖然通過(guò)上面的屬性可以訪(fǎng)問(wèn)到 location 對(duì)象的大多數(shù)信息,但其中訪(fǎng)問(wèn) URL 包含的查詢(xún)字符

串的屬性并不方便。盡管 location.search 返回從問(wèn)號(hào)到 URL 末尾的所有內(nèi)容,但卻沒(méi)有辦法逐個(gè)

訪(fǎng)問(wèn)其中的每個(gè)查詢(xún)字符串參數(shù)。為此,可以像下面這樣創(chuàng)建一個(gè)函數(shù),用以解析查詢(xún)字符串,然后返

回包含所有參數(shù)的一個(gè)對(duì)象:

function getQueryStringArgs(){

//取得查詢(xún)字符串并去掉開(kāi)頭的問(wèn)號(hào)

var qs = (location.search.length > 0 ? location.search.substring(1) : \"\"),

//保存數(shù)據(jù)的對(duì)象

args = {},

//取得每一項(xiàng)

items = qs.length ? qs.split(\"&\") : [],

item = null,

name = null,

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

第226頁(yè)

208 第 8 章 BOM

value = null,

//在 for 循環(huán)中使用

i = 0,

len = items.length;

//逐個(gè)將每一項(xiàng)添加到 args 對(duì)象中

for (i=0; i < len; i++){

item = items[i].split(\"=\");

name = decodeURIComponent(item[0]);

value = decodeURIComponent(item[1]);

if (name.length) {

args[name] = value;

}

}

return args;

}

LocationExample01.htm

這個(gè)函數(shù)的第一步是先去掉查詢(xún)字符串開(kāi)頭的問(wèn)號(hào)。當(dāng)然,前提是 location.search 中必須要

包含一或多個(gè)字符。然后,所有參數(shù)將被保存在 args 對(duì)象中,該對(duì)象以字面量形式創(chuàng)建。接下來(lái),

根據(jù)和號(hào)(&)來(lái)分割查詢(xún)字符串,并返回 name=value 格式的字符串?dāng)?shù)組。下面的 for 循環(huán)會(huì)迭代

這個(gè)數(shù)組,然后再根據(jù)等于號(hào)分割每一項(xiàng),從而返回第一項(xiàng)為參數(shù)名,第二項(xiàng)為參數(shù)值的數(shù)組。再使

用 decodeURIComponent()分別解碼 name 和 value(因?yàn)椴樵?xún)字符串應(yīng)該是被編碼過(guò)的)。最后,

將 name 作為 args 對(duì)象的屬性,將 value 作為相應(yīng)屬性的值。下面給出了使用這個(gè)函數(shù)的示例。

//假設(shè)查詢(xún)字符串是?q=javascript&num=10

var args = getQueryStringArgs();

alert(args[\"q\"]); //\"javascript\"

alert(args[\"num\"]); //\"10\"

可見(jiàn),每個(gè)查詢(xún)字符串參數(shù)都成了返回對(duì)象的屬性。這樣就極大地方便了對(duì)每個(gè)參數(shù)的訪(fǎng)問(wèn)。

8.2.2 位置操作

使用 location 對(duì)象可以通過(guò)很多方式來(lái)改變?yōu)g覽器的位置。首先,也是最常用的方式,就是使用

assign()方法并為其傳遞一個(gè) URL,如下所示。

location.assign(\"http://www.wrox.com\");

這樣,就可以立即打開(kāi)新 URL 并在瀏覽器的歷史記錄中生成一條記錄。如果是將 location.href

或 window.location 設(shè)置為一個(gè) URL 值,也會(huì)以該值調(diào)用 assign()方法。例如,下列兩行代碼與

顯式調(diào)用 assign()方法的效果完全一樣。

window.location = \"http://www.wrox.com\";

location.href = \"http://www.wrox.com\";

在這些改變?yōu)g覽器位置的方法中,最常用的是設(shè)置 location.href 屬性。

另外,修改location對(duì)象的其他屬性也可以改變當(dāng)前加載的頁(yè)面。下面的例子展示了通過(guò)將hash、

search、hostname、pathname 和 port 屬性設(shè)置為新值來(lái)改變 URL。

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

第227頁(yè)

8.2 location 對(duì)象 209

1

2

3

4

5

13

6

7

8

9

10

11

12

//假設(shè)初始 URL 為 http://www.wrox.com/WileyCDA/

//將 URL 修改為\"http://www.wrox.com/WileyCDA/#section1\"

location.hash = \"#section1\";

//將 URL 修改為\"http://www.wrox.com/WileyCDA/?q=javascript\"

location.search = \"?q=javascript\";

//將 URL 修改為\"http://www.yahoo.com/WileyCDA/\"

location.hostname = \"www.yahoo.com\";

//將 URL 修改為\"http://www.yahoo.com/mydir/\"

location.pathname = \"mydir\";

//將 URL 修改為\"http://www.yahoo.com:8080/WileyCDA/\"

location.port = 8080;

每次修改 location 的屬性(hash 除外),頁(yè)面都會(huì)以新 URL 重新加載。

在 IE8、Firefox 1、Safari 2+、Opera 9+和 Chrome 中,修改 hash 的值會(huì)在瀏覽

器的歷史記錄中生成一條新記錄。在 IE 的早期版本中,hash 屬性不會(huì)在用戶(hù)單擊“后

退”和“前進(jìn)”按鈕時(shí)被更新,而只會(huì)在用戶(hù)單擊包含 hash 的 URL 時(shí)才會(huì)被更新。

當(dāng)通過(guò)上述任何一種方式修改 URL 之后,瀏覽器的歷史記錄中就會(huì)生成一條新記錄,因此用戶(hù)通

過(guò)單擊“后退”按鈕都會(huì)導(dǎo)航到前一個(gè)頁(yè)面。要禁用這種行為,可以使用 replace()方法。這個(gè)方法

只接受一個(gè)參數(shù),即要導(dǎo)航到的 URL;結(jié)果雖然會(huì)導(dǎo)致瀏覽器位置改變,但不會(huì)在歷史記錄中生成新記

錄。在調(diào)用 replace()方法之后,用戶(hù)不能回到前一個(gè)頁(yè)面,來(lái)看下面的例子:

<!DOCTYPE html>

<html>

<head>

<title>You won't be able to get back here</title>

</head>

<body>

<p>Enjoy this page for a second, because you won't be coming back here.</p>

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

setTimeout(function () {

location.replace(\"http://www.wrox.com/\");

}, 1000);

</script>

</body>

</html>

LocationReplaceExample01.htm

如果將這個(gè)頁(yè)面加載到瀏覽器中,瀏覽器就會(huì)在 1 秒鐘后重新定向到 www.wrox.com。然后,“后退”

按鈕將處于禁用狀態(tài),如果不重新輸入完整的 URL,則無(wú)法返回示例頁(yè)面。

與位置有關(guān)的最后一個(gè)方法是 reload(),作用是重新加載當(dāng)前顯示的頁(yè)面。如果調(diào)用 reload()

時(shí)不傳遞任何參數(shù),頁(yè)面就會(huì)以最有效的方式重新加載。也就是說(shuō),如果頁(yè)面自上次請(qǐng)求以來(lái)并沒(méi)有改

變過(guò),頁(yè)面就會(huì)從瀏覽器緩存中重新加載。如果要強(qiáng)制從服務(wù)器重新加載,則需要像下面這樣為該方法

傳遞參數(shù) true。

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

第228頁(yè)

210 第 8 章 BOM

location.reload(); //重新加載(有可能從緩存中加載)

location.reload(true); //重新加載(從服務(wù)器重新加載)

位于 reload()調(diào)用之后的代碼可能會(huì)也可能不會(huì)執(zhí)行,這要取決于網(wǎng)絡(luò)延遲或系統(tǒng)資源等因素。

為此,最好將 reload()放在代碼的最后一行。

8.3 navigator 對(duì)象

最早由 Netscape Navigator 2.0 引入的 navigator 對(duì)象,現(xiàn)在已經(jīng)成為識(shí)別客戶(hù)端瀏覽器的事實(shí)標(biāo)

準(zhǔn)。雖然其他瀏覽器也通過(guò)其他方式提供了相同或相似的信息(例如,IE 中的 window.clientInformation 和 Opera 中的 window.opera),但 navigator 對(duì)象卻是所有支持 JavaScript 的瀏覽器所共有

的。與其他 BOM 對(duì)象的情況一樣,每個(gè)瀏覽器中的 navigator 對(duì)象也都有一套自己的屬性。下表列

出了存在于所有瀏覽器中的屬性和方法,以及支持它們的瀏覽器版本。

屬性或方法 說(shuō) 明 IE Firefox Safari/

Chrome Opera

appCodeName 瀏覽器的名稱(chēng)。通常都是Mozilla,即

使在非Mozilla瀏覽器中也是如此

3.0+ 1.0+ 1.0+ 7.0+

appMinorVersion 次版本信息 4.0+ - - 9.5+

appName 完整的瀏覽器名稱(chēng) 3.0+ 1.0+ 1.0+ 7.0+

appVersion 瀏覽器的版本。一般不與實(shí)際的瀏覽器

版本對(duì)應(yīng)

3.0+ 1.0+ 1.0+ 7.0+

buildID 瀏覽器編譯版本 - 2.0+ - -

cookieEnabled 表示cookie是否啟用 4.0+ 1.0+ 1.0+ 7.0+

cpuClass 客戶(hù)端計(jì)算機(jī)中使用的CPU類(lèi)型(x86、

68K、Alpha、PPC或Other)

4.0+ - - -

javaEnabled() 表示當(dāng)前瀏覽器中是否啟用了Java 4.0+ 1.0+ 1.0+ 7.0+

language 瀏覽器的主語(yǔ)言 - 1.0+ 1.0+ 7.0+

mimeTypes 在瀏覽器中注冊(cè)的MIME類(lèi)型數(shù)組 4.0+ 1.0+ 1.0+ 7.0+

onLine 表示瀏覽器是否連接到了因特網(wǎng) 4.0+ 1.0+ - 9.5+

opsProfile 似乎早就不用了。查不到相關(guān)文檔 4.0+ - - -

oscpu 客戶(hù)端計(jì)算機(jī)的操作系統(tǒng)或使用的CPU - 1.0+ - -

platform 瀏覽器所在的系統(tǒng)平臺(tái) 4.0+ 1.0+ 1.0+ 7.0+

plugins 瀏覽器中安裝的插件信息的數(shù)組 4.0+ 1.0+ 1.0+ 7.0+

preference() 設(shè)置用戶(hù)的首選項(xiàng) - 1.5+ - -

product 產(chǎn)品名稱(chēng)(如 Gecko) - 1.0+ 1.0+ -

productSub 關(guān)于產(chǎn)品的次要信息(如Gecko的版本) - 1.0+ 1.0+ -

registerContentHandler() 針對(duì)特定的MIME類(lèi)型將一個(gè)站點(diǎn)注冊(cè)

為處理程序

- 2.0+ - -

registerProtocolHandler() 針對(duì)特定的協(xié)議將一個(gè)站點(diǎn)注冊(cè)為處

理程序

- 2.0 - -

securityPolicy 已經(jīng)廢棄。安全策略的名稱(chēng)。為了與

Netscape Navigator 4向后兼容而保留下來(lái)

- 1.0+ - -

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

第229頁(yè)

8.3 navigator 對(duì)象 211

1

2

3

4

5

13

6

7

8

9

10

11

12

(續(xù))

屬性或方法 說(shuō) 明 IE Firefox Safari/

Chrome Opera

systemLanguage 操作系統(tǒng)的語(yǔ)言 4.0+ - - -

taintEnabled() 已經(jīng)廢棄。表示是否允許變量被修改

(taint)。為了與Netscape Navigator 3向后

兼容而保留下來(lái)

4.0+ 1.0+ - 7.0+

userAgent 瀏覽器的用戶(hù)代理字符串 3.0+ 1.0+ 1.0+ 7.0+

userLanguage 操作系統(tǒng)的默認(rèn)語(yǔ)言 4.0+ - - 7.0+

userProfile 借以訪(fǎng)問(wèn)用戶(hù)個(gè)人信息的對(duì)象 4.0+ - - -

vendor 瀏覽器的品牌 - 1.0+ 1.0+ -

vendorSub 有關(guān)供應(yīng)商的次要信息 - 1.0+ 1.0+ -

表中的這些 navigator 對(duì)象的屬性通常用于檢測(cè)顯示網(wǎng)頁(yè)的瀏覽器類(lèi)型(第 9 章會(huì)詳細(xì)討論)。

8.3.1 檢測(cè)插件

檢測(cè)瀏覽器中是否安裝了特定的插件是一種最常見(jiàn)的檢測(cè)例程。對(duì)于非 IE 瀏覽器,可以使用

plugins 數(shù)組來(lái)達(dá)到這個(gè)目的。該數(shù)組中的每一項(xiàng)都包含下列屬性。

? name:插件的名字。

? description:插件的描述。

? filename:插件的文件名。

? length:插件所處理的 MIME 類(lèi)型數(shù)量。

一般來(lái)說(shuō),name 屬性中會(huì)包含檢測(cè)插件必需的所有信息,但有時(shí)候也不完全如此。在檢測(cè)插件時(shí),

需要像下面這樣循環(huán)迭代每個(gè)插件并將插件的 name 與給定的名字進(jìn)行比較。

//檢測(cè)插件(在 IE 中無(wú)效)

function hasPlugin(name){

name = name.toLowerCase();

for (var i=0; i < navigator.plugins.length; i++){

if (navigator. plugins [i].name.toLowerCase().indexOf(name) > -1){

return true;

}

}

return false;

}

//檢測(cè) Flash

alert(hasPlugin(\"Flash\"));

//檢測(cè) QuickTime

alert(hasPlugin(\"QuickTime\"));

PluginDetectionExample01.htm

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

第230頁(yè)

212 第 8 章 BOM

這個(gè) hasPlugin()函數(shù)接受一個(gè)參數(shù):要檢測(cè)的插件名。第一步是將傳入的名稱(chēng)轉(zhuǎn)換為小寫(xiě)形式,

以便于比較。然后,迭代 plugins 數(shù)組,通過(guò) indexOf()檢測(cè)每個(gè) name 屬性,以確定傳入的名稱(chēng)是

否出現(xiàn)在字符串的某個(gè)地方。比較的字符串都使用小寫(xiě)形式可以避免因大小寫(xiě)不一致導(dǎo)致的錯(cuò)誤。而傳

入的參數(shù)應(yīng)該盡可能具體,以避免混淆。應(yīng)該說(shuō),像 Flash 和 QuickTime 這樣的字符串就比較具體了,

不容易導(dǎo)致混淆。在 Firefox、Safari、Opera 和 Chrome 中可以使用這種方法來(lái)檢測(cè)插件。

每個(gè)插件對(duì)象本身也是一個(gè) MimeType 對(duì)象的數(shù)組,這些對(duì)象可以通過(guò)方括號(hào)語(yǔ)

法來(lái)訪(fǎng)問(wèn)。每個(gè) MimeType 對(duì)象有 4 個(gè)屬性:包含 MIME 類(lèi)型描述的 description、

回指插件對(duì)象的 enabledPlugin、表示與 MIME 類(lèi)型對(duì)應(yīng)的文件擴(kuò)展名的字符串

suffixes(以逗號(hào)分隔)和表示完整 MIME 類(lèi)型字符串的 type。

檢測(cè) IE 中的插件比較麻煩,因?yàn)?IE 不支持 Netscape 式的插件。在 IE 中檢測(cè)插件的唯一方式就是

使用專(zhuān)有的 ActiveXObject 類(lèi)型,并嘗試創(chuàng)建一個(gè)特定插件的實(shí)例。IE 是以 COM 對(duì)象的方式實(shí)現(xiàn)插

件的,而 COM 對(duì)象使用唯一標(biāo)識(shí)符來(lái)標(biāo)識(shí)。因此,要想檢查特定的插件,就必須知道其 COM 標(biāo)識(shí)符。

例如,F(xiàn)lash 的標(biāo)識(shí)符是 ShockwaveFlash.ShockwaveFlash。知道唯一標(biāo)識(shí)符之后,就可以編寫(xiě)類(lèi)似

下面的函數(shù)來(lái)檢測(cè) IE 中是否安裝相應(yīng)插件了。

//檢測(cè) IE 中的插件

function hasIEPlugin(name){

try {

new ActiveXObject(name);

return true;

} catch (ex){

return false;

}

}

//檢測(cè) Flash

alert(hasIEPlugin(\"ShockwaveFlash.ShockwaveFlash\"));

//檢測(cè) QuickTime

alert(hasIEPlugin(\"QuickTime.QuickTime\"));

PluginDetectionExample02.htm

在這個(gè)例子中,函數(shù) hasIEPlugin()只接收一個(gè) COM 標(biāo)識(shí)符作為參數(shù)。在函數(shù)內(nèi)部,首先會(huì)嘗

試創(chuàng)建一個(gè) COM 對(duì)象的實(shí)例。之所以要在 try-catch 語(yǔ)句中進(jìn)行實(shí)例化,是因?yàn)閯?chuàng)建未知 COM 對(duì)象

會(huì)導(dǎo)致拋出錯(cuò)誤。這樣,如果實(shí)例化成功,則函數(shù)返回 true;否則,如果拋出了錯(cuò)誤,則執(zhí)行 catch

塊,結(jié)果就會(huì)返回 false。例子最后檢測(cè) IE 中是否安裝了 Flash 和 QuickTime 插件。

鑒于檢測(cè)這兩種插件的方法差別太大,因此典型的做法是針對(duì)每個(gè)插件分別創(chuàng)建檢測(cè)函數(shù),而不是

使用前面介紹的通用檢測(cè)方法。來(lái)看下面的例子。

//檢測(cè)所有瀏覽器中的 Flash

function hasFlash(){

var result = hasPlugin(\"Flash\");

if (!result){

result = hasIEPlugin(\"ShockwaveFlash.ShockwaveFlash\");

}

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

第231頁(yè)

8.3 navigator 對(duì)象 213

1

2

3

4

5

13

6

7

8

9

10

11

12

return result;

}

//檢測(cè)所有瀏覽器中的 QuickTime

function hasQuickTime(){

var result = hasPlugin(\"QuickTime\");

if (!result){

result = hasIEPlugin(\"QuickTime.QuickTime\");

}

return result;

}

//檢測(cè) Flash

alert(hasFlash());

//檢測(cè) QuickTime

alert(hasQuickTime());

PluginDetectionExample03.htm

上面代碼中定義了兩個(gè)函數(shù):hasFlash()和 hasQuickTime()。每個(gè)函數(shù)都是先嘗試使用不針對(duì)

IE 的插件檢測(cè)方法。如果返回了 false(在 IE 中會(huì)這樣),那么再使用針對(duì) IE 的插件檢測(cè)方法。如果

IE 的插件檢測(cè)方法再返回 false,則整個(gè)方法也將返回 false。只要任何一次檢測(cè)返回 true,整個(gè)方

法都會(huì)返回 true。

plugins 集合有一個(gè)名叫 refresh()的方法,用于刷新 plugins 以反映最新安

裝的插件。這個(gè)方法接收一個(gè)參數(shù):表示是否應(yīng)該重新加載頁(yè)面的一個(gè)布爾值。如果

將這個(gè)值設(shè)置為 true,則會(huì)重新加載包含插件的所有頁(yè)面;否則,只更新 plugins

集合,不重新加載頁(yè)面。

8.3.2 注冊(cè)處理程序

Firefox 2為 navigator 對(duì)象新增了 registerContentHandler()和 registerProtocolHandler()方

法(這兩個(gè)方法是在 HTML5 中定義的,相關(guān)內(nèi)容將在第 22 章討論)。這兩個(gè)方法可以讓一個(gè)站點(diǎn)指明

它可以處理特定類(lèi)型的信息。隨著 RSS 閱讀器和在線(xiàn)電子郵件程序的興起,注冊(cè)處理程序就為像使用桌

面應(yīng)用程序一樣默認(rèn)使用這些在線(xiàn)應(yīng)用程序提供了一種方式。

其中,registerContentHandler()方法接收三個(gè)參數(shù):要處理的 MIME 類(lèi)型、可以處理該 MIME

類(lèi)型的頁(yè)面的 URL 以及應(yīng)用程序的名稱(chēng)。舉個(gè)例子,要將一個(gè)站點(diǎn)注冊(cè)為處理 RSS 源的處理程序,可

以使用如下代碼。

navigator.registerContentHandler(\"application/rss+xml\",

\"http://www.somereader.com?feed=%s\", \"Some Reader\");

第一個(gè)參數(shù)是 RSS 源的 MIME 類(lèi)型。第二個(gè)參數(shù)是應(yīng)該接收 RSS 源 URL 的 URL,其中的%s 表示

RSS 源 URL,由瀏覽器自動(dòng)插入。當(dāng)下一次請(qǐng)求 RSS 源時(shí),瀏覽器就會(huì)打開(kāi)指定的 URL,而相應(yīng)的

Web 應(yīng)用程序?qū)⒁赃m當(dāng)方式來(lái)處理該請(qǐng)求。

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

第232頁(yè)

214 第 8 章 BOM

Firefox 4 及之前版本只允許在 registerContentHandler()方法中使用三個(gè)

MIME 類(lèi)型:application/rss+xml、application/atom+xml 和 application/

vnd.mozilla.maybe. feed。這三個(gè) MIME 類(lèi)型的作用都一樣,即為 RSS 或 ATOM

新聞源(feed)注冊(cè)處理程序。

類(lèi)似的調(diào)用方式也適用于 registerProtocolHandler()方法,它也接收三個(gè)參數(shù):要處理的協(xié)

議(例如,mailto 或 ftp)、處理該協(xié)議的頁(yè)面的 URL 和應(yīng)用程序的名稱(chēng)。例如,要想將一個(gè)應(yīng)用程

序注冊(cè)為默認(rèn)的郵件客戶(hù)端,可以使用如下代碼。

navigator.registerProtocolHandler(\"mailto\",

\"http://www.somemailclient.com?cmd=%s\", \"Some Mail Client\");

這個(gè)例子注冊(cè)了一個(gè) mailto 協(xié)議的處理程序,該程序指向一個(gè)基于 Web 的電子郵件客戶(hù)端。同樣,

第二個(gè)參數(shù)仍然是處理相應(yīng)請(qǐng)求的 URL,而%s 則表示原始的請(qǐng)求。

Firefox 2 雖然實(shí)現(xiàn)了 registerProtocolHandler(),但該方法還不能用。

Firefox 3 完整實(shí)現(xiàn)這個(gè)方法。

8.4 screen 對(duì)象

JavaScript 中有幾個(gè)對(duì)象在編程中用處不大,而 screen 對(duì)象就是其中之一。screen 對(duì)象基本上只

用來(lái)表明客戶(hù)端的能力,其中包括瀏覽器窗口外部的顯示器的信息,如像素寬度和高度等。每個(gè)瀏覽器

中的 screen 對(duì)象都包含著各不相同的屬性,下表列出了所有屬性及支持相應(yīng)屬性的瀏覽器。

屬 性 說(shuō) 明 IE Firefox Safari/

Chrome Opera

availHeight 屏幕的像素高度減系統(tǒng)部件高度之后的值(只讀) ? ? ? ?

availLeft 未被系統(tǒng)部件占用的最左側(cè)的像素值(只讀) ? ?

availTop 未被系統(tǒng)部件占用的最上方的像素值(只讀) ? ?

availWidth 屏幕的像素寬度減系統(tǒng)部件寬度之后的值(只讀) ? ? ? ?

bufferDepth 讀、寫(xiě)用于呈現(xiàn)屏外位圖的位數(shù) ?

colorDepth 用于表現(xiàn)顏色的位數(shù);多數(shù)系統(tǒng)都是32(只讀) ? ? ? ?

deviceXDPI 屏幕實(shí)際的水平DPI(只讀) ?

deviceYDPI 屏幕實(shí)際的垂直DPI(只讀) ?

fontSmoothingEnabled 表示是否啟用了字體平滑(只讀) ?

height 屏幕的像素高度 ? ? ? ?

left 當(dāng)前屏幕距左邊的像素距離 ?

logicalXDPI 屏幕邏輯的水平DPI(只讀) ?

logicalYDPI 屏幕邏輯的垂直DPI(只讀) ?

pixelDepth 屏幕的位深(只讀) ? ? ?

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

第233頁(yè)

8.5 history 對(duì)象 215

1

2

3

4

5

13

6

7

8

9

10

11

12

(續(xù))

屬 性 說(shuō) 明 IE Firefox Safari/

Chrome Opera

top 當(dāng)前屏幕距上邊的像素距離 ?

updateInterval 讀、寫(xiě)以毫秒表示的屏幕刷新時(shí)間間隔 ?

width 屏幕的像素寬度 ? ? ? ?

這些信息經(jīng)常集中出現(xiàn)在測(cè)定客戶(hù)端能力的站點(diǎn)跟蹤工具中,但通常不會(huì)用于影響功能。不過(guò),有

時(shí)候也可能會(huì)用到其中的信息來(lái)調(diào)整瀏覽器窗口大小,使其占據(jù)屏幕的可用空間,例如:

window.resizeTo(screen.availWidth, screen.availHeight);

前面曾經(jīng)提到過(guò),許多瀏覽器都會(huì)禁用調(diào)整瀏覽器窗口大小的能力,因此上面這行代碼不一定在所

有環(huán)境下都有效。

涉及移動(dòng)設(shè)備的屏幕大小時(shí),情況有點(diǎn)不一樣。運(yùn)行 iOS 的設(shè)備始終會(huì)像是把設(shè)備豎著拿在手里一

樣,因此返回的值是 768×1024。而 Android 設(shè)備則會(huì)相應(yīng)調(diào)用 screen.width 和 screen.height 的值。

8.5 history 對(duì)象

history 對(duì)象保存著用戶(hù)上網(wǎng)的歷史記錄,從窗口被打開(kāi)的那一刻算起。因?yàn)?history 是 window

對(duì)象的屬性,因此每個(gè)瀏覽器窗口、每個(gè)標(biāo)簽頁(yè)乃至每個(gè)框架,都有自己的 history 對(duì)象與特定的

window 對(duì)象關(guān)聯(lián)。出于安全方面的考慮,開(kāi)發(fā)人員無(wú)法得知用戶(hù)瀏覽過(guò)的 URL。不過(guò),借由用戶(hù)訪(fǎng)問(wèn)

過(guò)的頁(yè)面列表,同樣可以在不知道實(shí)際 URL 的情況下實(shí)現(xiàn)后退和前進(jìn)。

使用 go()方法可以在用戶(hù)的歷史記錄中任意跳轉(zhuǎn),可以向后也可以向前。這個(gè)方法接受一個(gè)參數(shù),

表示向后或向前跳轉(zhuǎn)的頁(yè)面數(shù)的一個(gè)整數(shù)值。負(fù)數(shù)表示向后跳轉(zhuǎn)(類(lèi)似于單擊瀏覽器的“后退”按鈕),

正數(shù)表示向前跳轉(zhuǎn)(類(lèi)似于單擊瀏覽器的“前進(jìn)”按鈕)。來(lái)看下面的例子。

//后退一頁(yè)

history.go(-1);

//前進(jìn)一頁(yè)

history.go(1);

//前進(jìn)兩頁(yè)

history.go(2);

也可以給 go()方法傳遞一個(gè)字符串參數(shù),此時(shí)瀏覽器會(huì)跳轉(zhuǎn)到歷史記錄中包含該字符串的第一個(gè)

位置——可能后退,也可能前進(jìn),具體要看哪個(gè)位置最近。如果歷史記錄中不包含該字符串,那么這個(gè)

方法什么也不做,例如:

//跳轉(zhuǎn)到最近的 wrox.com 頁(yè)面

history.go(\"wrox.com\");

//跳轉(zhuǎn)到最近的 nczonline.net 頁(yè)面

history.go(\"nczonline.net\");

另外,還可以使用兩個(gè)簡(jiǎn)寫(xiě)方法 back()和 forward()來(lái)代替 go()。顧名思義,這兩個(gè)方法可以

模仿瀏覽器的“后退”和“前進(jìn)”按鈕。

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

第234頁(yè)

216 第 8 章 BOM

//后退一頁(yè)

history.back();

//前進(jìn)一頁(yè)

history.forward();

除了上述幾個(gè)方法外,history 對(duì)象還有一個(gè) length 屬性,保存著歷史記錄的數(shù)量。這個(gè)數(shù)量

包括所有歷史記錄,即所有向后和向前的記錄。對(duì)于加載到窗口、標(biāo)簽頁(yè)或框架中的第一個(gè)頁(yè)面而言,

history.length 等于 0。通過(guò)像下面這樣測(cè)試該屬性的值,可以確定用戶(hù)是否一開(kāi)始就打開(kāi)了你的

頁(yè)面。

if (history.length == 0){

//這應(yīng)該是用戶(hù)打開(kāi)窗口后的第一個(gè)頁(yè)面

}

雖然 history 并不常用,但在創(chuàng)建自定義的“后退”和“前進(jìn)”按鈕,以及檢測(cè)當(dāng)前頁(yè)面是不是

用戶(hù)歷史記錄中的第一個(gè)頁(yè)面時(shí),還是必須使用它。

當(dāng)頁(yè)面的 URL 改變時(shí),就會(huì)生成一條歷史記錄。在 IE8 及更高版本、Opera、

Firefox、Safari 3 及更高版本以及 Chrome 中,這里所說(shuō)的改變包括 URL 中 hash 的變

化(因此,設(shè)置 location.hash 會(huì)在這些瀏覽器中生成一條新的歷史記錄)。

8.6 小結(jié)

瀏覽器對(duì)象模型(BOM)以 window 對(duì)象為依托,表示瀏覽器窗口以及頁(yè)面可見(jiàn)區(qū)域。同時(shí),window

對(duì)象還是 ECMAScript 中的 Global 對(duì)象,因而所有全局變量和函數(shù)都是它的屬性,且所有原生的構(gòu)造

函數(shù)及其他函數(shù)也都存在于它的命名空間下。本章討論了下列 BOM 的組成部分。

? 在使用框架時(shí),每個(gè)框架都有自己的 window 對(duì)象以及所有原生構(gòu)造函數(shù)及其他函數(shù)的副本。

每個(gè)框架都保存在 frames 集合中,可以通過(guò)位置或通過(guò)名稱(chēng)來(lái)訪(fǎng)問(wèn)。

? 有一些窗口指針,可以用來(lái)引用其他框架,包括父框架。

? top 對(duì)象始終指向最外圍的框架,也就是整個(gè)瀏覽器窗口。

? parent 對(duì)象表示包含當(dāng)前框架的框架,而 self 對(duì)象則回指 window。

? 使用 location 對(duì)象可以通過(guò)編程方式來(lái)訪(fǎng)問(wèn)瀏覽器的導(dǎo)航系統(tǒng)。設(shè)置相應(yīng)的屬性,可以逐段

或整體性地修改瀏覽器的 URL。

? 調(diào)用 replace()方法可以導(dǎo)航到一個(gè)新 URL,同時(shí)該 URL 會(huì)替換瀏覽器歷史記錄中當(dāng)前顯示

的頁(yè)面。

? navigator 對(duì)象提供了與瀏覽器有關(guān)的信息。到底提供哪些信息,很大程度上取決于用戶(hù)的瀏

覽器;不過(guò),也有一些公共的屬性(如 userAgent)存在于所有瀏覽器中。

BOM 中還有兩個(gè)對(duì)象:screen 和 history,但它們的功能有限。screen 對(duì)象中保存著與客戶(hù)端

顯示器有關(guān)的信息,這些信息一般只用于站點(diǎn)分析。history 對(duì)象為訪(fǎng)問(wèn)瀏覽器的歷史記錄開(kāi)了一個(gè)

小縫隙,開(kāi)發(fā)人員可以據(jù)此判斷歷史記錄的數(shù)量,也可以在歷史記錄中向后或向前導(dǎo)航到任意頁(yè)面。

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

第235頁(yè)

9.1 能力檢測(cè) 217

1

2

3

4

5

13

6

7

8

9

10

11

12

客戶(hù)端檢測(cè)

本章內(nèi)容

? 使用能力檢測(cè)

? 用戶(hù)代理檢測(cè)的歷史

? 選擇檢測(cè)方式

覽器提供商雖然在實(shí)現(xiàn)公共接口方面投入了很多精力,但結(jié)果仍然是每一種瀏覽器都有各自

的長(zhǎng)處,也都有各自的缺點(diǎn)。即使是那些跨平臺(tái)的瀏覽器,雖然從技術(shù)上看版本相同,也照

樣存在不一致性問(wèn)題。面對(duì)普遍存在的不一致性問(wèn)題,開(kāi)發(fā)人員要么采取遷就各方的“最小公分母”策

略,要么(也是更常見(jiàn)的)就得利用各種客戶(hù)端檢測(cè)方法,來(lái)突破或者規(guī)避種種局限性。

迄今為止,客戶(hù)端檢測(cè)仍然是 Web 開(kāi)發(fā)領(lǐng)域中一個(gè)飽受爭(zhēng)議的話(huà)題。一談到這個(gè)話(huà)題,人們總會(huì)

不約而同地提到瀏覽器應(yīng)該支持一組最常用的公共功能。在理想狀態(tài)下,確實(shí)應(yīng)該如此。但是,在現(xiàn)實(shí)

當(dāng)中,瀏覽器之間的差異以及不同瀏覽器的“怪癖”(quirk),多得簡(jiǎn)直不勝枚舉。因此,客戶(hù)端檢測(cè)除

了是一種補(bǔ)救措施之外,更是一種行之有效的開(kāi)發(fā)策略。

檢測(cè) Web 客戶(hù)端的手段很多,而且各有利弊。但最重要的還是要知道,不到萬(wàn)不得已,就不要使

用客戶(hù)端檢測(cè)。只要能找到更通用的方法,就應(yīng)該優(yōu)先采用更通用的方法。一言以蔽之,先設(shè)計(jì)最通用

的方案,然后再使用特定于瀏覽器的技術(shù)增強(qiáng)該方案。

9.1 能力檢測(cè)

最常用也最為人們廣泛接受的客戶(hù)端檢測(cè)形式是能力檢測(cè)(又稱(chēng)特性檢測(cè))。能力檢測(cè)的目標(biāo)不是

識(shí)別特定的瀏覽器,而是識(shí)別瀏覽器的能力。采用這種方式不必顧及特定的瀏覽器如何如何,只要確定

瀏覽器支持特定的能力,就可以給出解決方案。能力檢測(cè)的基本模式如下:

if (object.propertyInQuestion){

//使用 object.propertyInQuestion

}

舉例來(lái)說(shuō),IE5.0 之前的版本不支持 document.getElementById()這個(gè) DOM 方法。盡管可以使

用非標(biāo)準(zhǔn)的 document.all 屬性實(shí)現(xiàn)相同的目的,但 IE 的早期版本中確實(shí)不存在 document.getElementById()。于是,也就有了類(lèi)似下面的能力檢測(cè)代碼:

function getElement(id){

if (document.getElementById){

return document.getElementById(id);

} else if (document.all){

return document.all[id];

第 9 章

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

第236頁(yè)

218 第 9 章 客戶(hù)端檢測(cè)

} else {

throw new Error(\"No way to retrieve element!\");

}

}

這里的 getElement()函數(shù)的用途是返回具有給定 ID 的元素。因?yàn)?document.getElementById()

是實(shí)現(xiàn)這一目的的標(biāo)準(zhǔn)方式,所以一開(kāi)始就測(cè)試了這個(gè)方法。如果該函數(shù)存在(不是未定義),則使用

該函數(shù)。否則,就要繼續(xù)檢測(cè) document.all 是否存在,如果是,則使用它。如果上述兩個(gè)特性都不

存在(很有可能),則創(chuàng)建并拋出錯(cuò)誤,表示這個(gè)函數(shù)無(wú)法使用。

要理解能力檢測(cè),首先必須理解兩個(gè)重要的概念。如前所述,第一個(gè)概念就是先檢測(cè)達(dá)成目的的最常

用的特性。對(duì)前面的例子來(lái)說(shuō),就是要先檢測(cè) document.getElementById(),后檢測(cè) document.all。

先檢測(cè)最常用的特性可以保證代碼最優(yōu)化,因?yàn)樵诙鄶?shù)情況下都可以避免測(cè)試多個(gè)條件。

第二個(gè)重要的概念就是必須測(cè)試實(shí)際要用到的特性。一個(gè)特性存在,不一定意味著另一個(gè)特性也存

在。來(lái)看一個(gè)例子:

function getWindowWidth(){

if (document.all){ //假設(shè)是 IE

return document.documentElement.clientWidth; //錯(cuò)誤的用法?。?!

} else {

return window.innerWidth;

}

}

這是一個(gè)錯(cuò)誤使用能力檢測(cè)的例子。getWindowWidth()函數(shù)首先檢查 document.all 是否存在,

如果是則返回 document.documentElement.clientWidth。第 8 章曾經(jīng)討論過(guò),IE8 及之前版本確

實(shí)不支持 window.innerWidth 屬性。但問(wèn)題是 document.all 存在也不一定表示瀏覽器就是 IE。實(shí)

際上,也可能是 Opera;Opera 支持 document.all,也支持 window.innerWidth。

9.1.1 更可靠的能力檢測(cè)

能力檢測(cè)對(duì)于想知道某個(gè)特性是否會(huì)按照適當(dāng)方式行事(而不僅僅是某個(gè)特性存在)非常有用。上

一節(jié)中的例子利用類(lèi)型轉(zhuǎn)換來(lái)確定某個(gè)對(duì)象成員是否存在,但這樣你還是不知道該成員是不是你想要

的。來(lái)看下面的函數(shù),它用來(lái)確定一個(gè)對(duì)象是否支持排序。

//不要這樣做!這不是能力檢測(cè)——只檢測(cè)了是否存在相應(yīng)的方法

function isSortable(object){

return !!object.sort;

}

這個(gè)函數(shù)通過(guò)檢測(cè)對(duì)象是否存在 sort()方法,來(lái)確定對(duì)象是否支持排序。問(wèn)題是,任何包含 sort

屬性的對(duì)象也會(huì)返回 true。

var result = isSortable({ sort: true });

檢測(cè)某個(gè)屬性是否存在并不能確定對(duì)象是否支持排序。更好的方式是檢測(cè) sort 是不是一個(gè)函數(shù)。

//這樣更好:檢查 sort 是不是函數(shù)

function isSortable(object){

return typeof object.sort == \"function\";

}

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

第237頁(yè)

9.1 能力檢測(cè) 219

1

2

3

4

5

13

6

7

8

9

10

11

12

這里的 typeof 操作符用于確定 sort 的確是一個(gè)函數(shù),因此可以調(diào)用它對(duì)數(shù)據(jù)進(jìn)行排序。

在可能的情況下,要盡量使用 typeof 進(jìn)行能力檢測(cè)。特別是,宿主對(duì)象沒(méi)有義務(wù)讓 typeof 返回

合理的值。最令人發(fā)指的事兒就發(fā)生在 IE 中。大多數(shù)瀏覽器在檢測(cè)到 document.createElement()

存在時(shí),都會(huì)返回 true。

//在 IE8 及之前版本中不行

function hasCreateElement(){

return typeof document.createElement == \"function\";

}

在 IE8 及之前版本中,這個(gè)函數(shù)返回 false,因?yàn)?typeof document.createElement 返回的是

\"object\",而不是\"function\"。如前所述,DOM 對(duì)象是宿主對(duì)象,IE 及更早版本中的宿主對(duì)象是通

過(guò) COM 而非 JScript 實(shí)現(xiàn)的。因此,document.createElement()函數(shù)確實(shí)是一個(gè) COM 對(duì)象,所以

typeof 才會(huì)返回\"object\"。IE9 糾正了這個(gè)問(wèn)題,對(duì)所有 DOM 方法都返回\"function\"。

關(guān)于 typeof 的行為不標(biāo)準(zhǔn),IE 中還可以舉出例子來(lái)。ActiveX 對(duì)象(只有 IE 支持)與其他對(duì)象的行

為差異很大。例如,不使用 typeof 測(cè)試某個(gè)屬性會(huì)導(dǎo)致錯(cuò)誤,如下所示。

//在 IE 中會(huì)導(dǎo)致錯(cuò)誤

var xhr = new ActiveXObject(\"Microsoft.XMLHttp\");

if (xhr.open){ //這里會(huì)發(fā)生錯(cuò)誤

//執(zhí)行操作

}

像這樣直接把函數(shù)作為屬性訪(fǎng)問(wèn)會(huì)導(dǎo)致 JavaScript 錯(cuò)誤。使用 typeof 操作符會(huì)更靠譜一點(diǎn),但 IE

對(duì) typeof xhr.open 會(huì)返回\"unknown\"。這就意味著,在瀏覽器環(huán)境下測(cè)試任何對(duì)象的某個(gè)特性是否

存在,要使用下面這個(gè)函數(shù)。

//作者:Peter Michaux

function isHostMethod(object, property) {

var t = typeof object[property];

return t=='function' ||

(!!(t=='object' && object[property])) ||

t=='unknown';

}

可以像下面這樣使用這個(gè)函數(shù):

result = isHostMethod(xhr, \"open\"); //true

result = isHostMethod(xhr, \"foo\"); //false

目前使用 isHostMethod()方法還是比較可靠的,因?yàn)樗紤]到了瀏覽器的怪異行為。不過(guò)也要注

意,宿主對(duì)象沒(méi)有義務(wù)保持目前的實(shí)現(xiàn)方式不變,也不一定會(huì)模仿已有宿主對(duì)象的行為。所以,這個(gè)函

數(shù)——以及其他類(lèi)似函數(shù),都不能百分之百地保證永遠(yuǎn)可靠。作為開(kāi)發(fā)人員,必須對(duì)自己要使用某個(gè)功

能的風(fēng)險(xiǎn)作出理性的估計(jì)。

要想深入了解圍繞 JavaScript 中能力檢測(cè)的一些觀點(diǎn),請(qǐng)參考 Peter Michaux 的文

章“Feature Detection: State of the Art Browser Scripting”,網(wǎng)址為 http://peter.michaux.ca/

articles/feature-detection-state-of-the-art-browser-scripting。

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

第238頁(yè)

220 第 9 章 客戶(hù)端檢測(cè)

9.1.2 能力檢測(cè),不是瀏覽器檢測(cè)

檢測(cè)某個(gè)或某幾個(gè)特性并不能夠確定瀏覽器。下面給出的這段代碼(或與之差不多的代碼)可以在

許多網(wǎng)站中看到,這種“瀏覽器檢測(cè)”代碼就是錯(cuò)誤地依賴(lài)能力檢測(cè)的典型示例。

//錯(cuò)誤!還不夠具體

var isFirefox = !!(navigator.vendor && navigator.vendorSub);

//錯(cuò)誤!假設(shè)過(guò)頭了

var isIE = !!(document.all && document.uniqueID);

這兩行代碼代表了對(duì)能力檢測(cè)的典型誤用。以前,確實(shí)可以通過(guò)檢測(cè) navigator.vendor 和

navigator.vendorSub 來(lái)確定 Firefox 瀏覽器。但是,Safari 也依葫蘆畫(huà)瓢地實(shí)現(xiàn)了相同的屬性。于是,

這段代碼就會(huì)導(dǎo)致人們作出錯(cuò)誤的判斷。為檢測(cè) IE,代碼測(cè)試了 document.all 和 document.

uniqueID。這就相當(dāng)于假設(shè) IE 將來(lái)的版本中仍然會(huì)繼續(xù)存在這兩個(gè)屬性,同時(shí)還假設(shè)其他瀏覽器都不

會(huì)實(shí)現(xiàn)這兩個(gè)屬性。最后,這兩個(gè)檢測(cè)都使用了雙邏輯非操作符來(lái)得到布爾值(比先存儲(chǔ)后訪(fǎng)問(wèn)的效果

更好)。

實(shí)際上,根據(jù)瀏覽器不同將能力組合起來(lái)是更可取的方式。如果你知道自己的應(yīng)用程序需要使用某

些特定的瀏覽器特性,那么最好是一次性檢測(cè)所有相關(guān)特性,而不要分別檢測(cè)??聪旅娴睦?。

//確定瀏覽器是否支持 Netscape 風(fēng)格的插件

var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);

//確定瀏覽器是否具有 DOM1 級(jí)規(guī)定的能力

var hasDOM1 = !!(document.getElementById && document.createElement &&

document.getElementsByTagName);

CapabilitiesDetectionExample01.htm

以上例子展示了兩個(gè)檢測(cè):一個(gè)檢測(cè)瀏覽器是否支持 Netscapte 風(fēng)格的插件;另一個(gè)檢測(cè)瀏覽器是

否具備 DOM1 級(jí)所規(guī)定的能力。得到的布爾值可以在以后繼續(xù)使用,從而節(jié)省重新檢測(cè)能力的時(shí)間。

在實(shí)際開(kāi)發(fā)中,應(yīng)該將能力檢測(cè)作為確定下一步解決方案的依據(jù),而不是用它來(lái)

判斷用戶(hù)使用的是什么瀏覽器。

9.2 怪癖檢測(cè)

與能力檢測(cè)類(lèi)似,怪癖檢測(cè)(quirks detection)的目標(biāo)是識(shí)別瀏覽器的特殊行為。但與能力檢測(cè)確

認(rèn)瀏覽器支持什么能力不同,怪癖檢測(cè)是想要知道瀏覽器存在什么缺陷(“怪癖”也就是 bug)。這通常

需要運(yùn)行一小段代碼,以確定某一特性不能正常工作。例如,IE8 及更早版本中存在一個(gè) bug,即如果

某個(gè)實(shí)例屬性與[[Enumerable]]標(biāo)記為 false 的某個(gè)原型屬性同名,那么該實(shí)例屬性將不會(huì)出現(xiàn)在

fon-in 循環(huán)當(dāng)中。可以使用如下代碼來(lái)檢測(cè)這種“怪癖”。

var hasDontEnumQuirk = function(){

var o = { toString : function(){} };

for (var prop in o){

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

第239頁(yè)

9.3 用戶(hù)代理檢測(cè) 221

1

2

3

4

5

13

6

7

8

9

10

11

12

if (prop == \"toString\"){

return false;

}

}

return true;

}();

QuirksDetectionExample01.htm

以上代碼通過(guò)一個(gè)匿名函數(shù)來(lái)測(cè)試該“怪癖”,函數(shù)中創(chuàng)建了一個(gè)帶有 toString()方法的對(duì)象。

在正確的 ECMAScript 實(shí)現(xiàn)中,toString 應(yīng)該在 for-in 循環(huán)中作為屬性返回。

另一個(gè)經(jīng)常需要檢測(cè)的“怪癖”是 Safari 3 以前版本會(huì)枚舉被隱藏的屬性??梢杂孟旅娴暮瘮?shù)來(lái)檢

測(cè)該“怪癖”。

var hasEnumShadowsQuirk = function(){

var o = { toString : function(){} };

var count = 0;

for (var prop in o){

if (prop == \"toString\"){

count++;

}

}

return (count > 1);

}();

QuirksDetectionExample01.htm

如果瀏覽器存在這個(gè) bug,那么使用 for-in 循環(huán)枚舉帶有自定義的 toString()方法的對(duì)象,就

會(huì)返回兩個(gè) toString 的實(shí)例。

一般來(lái)說(shuō),“怪癖”都是個(gè)別瀏覽器所獨(dú)有的,而且通常被歸為 bug。在相關(guān)瀏覽器的新版本中,這

些問(wèn)題可能會(huì)也可能不會(huì)被修復(fù)。由于檢測(cè)“怪癖”涉及運(yùn)行代碼,因此我們建議僅檢測(cè)那些對(duì)你有直

接影響的“怪癖”,而且最好在腳本一開(kāi)始就執(zhí)行此類(lèi)檢測(cè),以便盡早解決問(wèn)題。

9.3 用戶(hù)代理檢測(cè)

第三種,也是爭(zhēng)議最大的一種客戶(hù)端檢測(cè)技術(shù)叫做用戶(hù)代理檢測(cè)。用戶(hù)代理檢測(cè)通過(guò)檢測(cè)用戶(hù)代理

字符串來(lái)確定實(shí)際使用的瀏覽器。在每一次 HTTP 請(qǐng)求過(guò)程中,用戶(hù)代理字符串是作為響應(yīng)首部發(fā)送的,

而且該字符串可以通過(guò) JavaScript 的 navigator.userAgent 屬性訪(fǎng)問(wèn)。在服務(wù)器端,通過(guò)檢測(cè)用戶(hù)代

理字符串來(lái)確定用戶(hù)使用的瀏覽器是一種常用而且廣為接受的做法。而在客戶(hù)端,用戶(hù)代理檢測(cè)一般被

當(dāng)作一種萬(wàn)不得已才用的做法,其優(yōu)先級(jí)排在能力檢測(cè)和(或)怪癖檢測(cè)之后。

提到與用戶(hù)代理字符串有關(guān)的爭(zhēng)議,就不得不提到電子欺騙(spoofing)。所謂電子欺騙,就是指瀏

覽器通過(guò)在自己的用戶(hù)代理字符串加入一些錯(cuò)誤或誤導(dǎo)性信息,來(lái)達(dá)到欺騙服務(wù)器的目的。要弄清楚這

個(gè)問(wèn)題的來(lái)龍去脈,必須從 Web 問(wèn)世初期用戶(hù)代理字符串的發(fā)展講起。

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

第240頁(yè)

222 第 9 章 客戶(hù)端檢測(cè)

9.3.1 用戶(hù)代理字符串的歷史

HTTP 規(guī)范(包括 1.0 和 1.1 版)明確規(guī)定,瀏覽器應(yīng)該發(fā)送簡(jiǎn)短的用戶(hù)代理字符串,指明瀏覽器的

名稱(chēng)和版本號(hào)。RFC 2616(即 HTTP 1.1 協(xié)議規(guī)范)是這樣描述用戶(hù)代理字符串的:

“產(chǎn)品標(biāo)識(shí)符常用于通信應(yīng)用程序標(biāo)識(shí)自身,由軟件名和版本組成。使用產(chǎn)品標(biāo)識(shí)符的大

多數(shù)領(lǐng)域也允許列出作為應(yīng)用程序主要部分的子產(chǎn)品,由空格分隔。按照慣例,產(chǎn)品要按照相

應(yīng)的重要程度依次列出,以便標(biāo)識(shí)應(yīng)用程序?!?/p>

上述規(guī)范進(jìn)一步規(guī)定,用戶(hù)代理字符串應(yīng)該以一組產(chǎn)品的形式給出,字符串格式為:標(biāo)識(shí)符/產(chǎn)品

版本號(hào)。但是,現(xiàn)實(shí)中的用戶(hù)代理字符串則絕沒(méi)有如此簡(jiǎn)單。

1. 早期的瀏覽器

1993 年,美國(guó) NCSA(National Center for Supercomputing Applications,國(guó)家超級(jí)計(jì)算機(jī)中心)發(fā)布

了世界上第一款 Web 瀏覽器 Mosaic。這款瀏覽器的用戶(hù)代理字符串非常簡(jiǎn)單,類(lèi)似如下所示。

Mosaic/0.9

盡管這個(gè)字符串在不同操作系統(tǒng)和不同平臺(tái)下會(huì)有所變化,但其基本格式還是簡(jiǎn)單明了的。正斜杠

前面的文本表示產(chǎn)品名稱(chēng)(有時(shí)候會(huì)出現(xiàn) NCSA Mosaic 或其他類(lèi)似字樣),而斜杠后面的文本是產(chǎn)品的

版本號(hào)。

Netscape Communications 公司介入瀏覽器開(kāi)發(fā)領(lǐng)域后,遂將自己產(chǎn)品的代號(hào)定名為 Mozilla(Mosaic

Killer 的簡(jiǎn)寫(xiě),意即 Mosaic 殺手)。該公司第一個(gè)公開(kāi)發(fā)行版,Netscape Navigator 2 的用戶(hù)代理字符串

具有如下格式。

Mozilla/版本號(hào) [語(yǔ)言] (平臺(tái); 加密類(lèi)型)

Netscape 在堅(jiān)持將產(chǎn)品名和版本號(hào)作為用戶(hù)代理字符串開(kāi)頭的基礎(chǔ)上,又在后面依次添加了下列

信息。

? 語(yǔ)言:即語(yǔ)言代碼,表示應(yīng)用程序針對(duì)哪種語(yǔ)言設(shè)計(jì)。

? 平臺(tái):即操作系統(tǒng)和(或)平臺(tái),表示應(yīng)用程序的運(yùn)行環(huán)境。

? 加密類(lèi)型:即安全加密的類(lèi)型??赡艿闹涤?U(128 位加密)、I(40 位加密)和 N(未加密)。

典型的 Netscape Navigator 2 的用戶(hù)代理字符串如下所示。

Mozilla/2.02 [fr] (WinNT; I)

這個(gè)字符串表示瀏覽器是 Netscape Navigator 2.02,為法語(yǔ)國(guó)家編譯,運(yùn)行在 Windows NT 平臺(tái)下,

加密類(lèi)型為 40 位。那個(gè)時(shí)候,通過(guò)用戶(hù)代理字符串中的產(chǎn)品名稱(chēng),至少還能夠輕易地確定用戶(hù)使用的

是什么瀏覽器。

2. Netscape Navigator 3 和 Internet Explorer 3

1996 年,Netscape Navigator 3 發(fā)布,隨即超越 Mosaic 成為當(dāng)時(shí)最流行的 Web 瀏覽器。而用戶(hù)代理

字符串只作了一些小的改變,刪除了語(yǔ)言標(biāo)記,同時(shí)允許添加操作系統(tǒng)或系統(tǒng)使用的 CPU 等可選信息。

于是,格式變成如下所示。

Mozilla/版本號(hào) (平臺(tái); 加密類(lèi)型 [; 操作系統(tǒng)或 CPU 說(shuō)明])

運(yùn)行在 Windows 系統(tǒng)下的 Netscape Navigator 3 的用戶(hù)代理字符串大致如下。

Mozilla/3.0 (Win95; U)

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

第241頁(yè)

9.3 用戶(hù)代理檢測(cè) 223

1

2

3

4

5

13

6

7

8

9

10

11

12

這個(gè)字符串表示 Netscape Navigator 3 運(yùn)行在 Windows 95 中,采用了 128 位加密技術(shù)??梢?jiàn),在

Windows 系統(tǒng)中,字符串中的操作系統(tǒng)或 CPU 說(shuō)明被省略了。

Netscape Navigator 3 發(fā)布后不久,微軟也發(fā)布了其第一款贏得用戶(hù)廣泛認(rèn)可的 Web 瀏覽器,即

Internet Explorer 3。由于 Netscape 瀏覽器在當(dāng)時(shí)占絕對(duì)市場(chǎng)份額,許多服務(wù)器在提供網(wǎng)頁(yè)之前都要專(zhuān)門(mén)

檢測(cè)該瀏覽器。如果用戶(hù)通過(guò) IE 打不開(kāi)相關(guān)網(wǎng)頁(yè),那么這個(gè)新生的瀏覽器很可能就會(huì)夭折。于是,微

軟決定將 IE 的用戶(hù)代理字符串修改成兼容 Netscape 的形式,結(jié)果如下:

Mozilla/2.0 (compatible; MSIE 版本號(hào); 操作系統(tǒng))

例如,Windows 95 平臺(tái)下的 Internet Explorer 3.02 帶有如下用戶(hù)代理字符串:

Mozilla/2.0 (compatible; MSIE 3.02; Windows 95)

由于當(dāng)時(shí)的大多數(shù)瀏覽器嗅探程序只檢測(cè)用戶(hù)代理字符串中的產(chǎn)品名稱(chēng)部分,結(jié)果 IE 就成功地將

自己標(biāo)識(shí)為 Mozilla,從而偽裝成 Netscape Navigator。微軟的這一做法招致了很多批評(píng),因?yàn)樗`反了

瀏覽器標(biāo)識(shí)的慣例。更不規(guī)范的是,IE 將真正的瀏覽器版本號(hào)插入到了字符串的中間。

字符串中另外一個(gè)有趣的地方是標(biāo)識(shí)符 Mozilla 2.0(而不是 3.0)。畢竟,當(dāng)時(shí)的主流版本是 3.0,

改成 3.0 應(yīng)該對(duì)微軟更有利才對(duì)。但真正的謎底到現(xiàn)在還沒(méi)有揭開(kāi)——但很可能只是人為疏忽所致。

3. Netscape Communicator 4 和 IE4~I(xiàn)E8

1997 年 8 月,Netscapte Communicator 4 發(fā)布(這一版將瀏覽器名字中的 Navigator 換成了 Communicator)。Netscape 繼續(xù)遵循了第 3 版時(shí)的用戶(hù)代理字符串格式:

Mozilla/版本號(hào) (平臺(tái); 加密類(lèi)型 [; 操作系統(tǒng)或 CPU 說(shuō)明])

因此,Windows 98 平臺(tái)中第 4 版的用戶(hù)代理字符串如下所示:

Mozilla/4.0 (Win98; I)

Netscape 在發(fā)布補(bǔ)丁時(shí),子版本號(hào)也會(huì)相應(yīng)提高,用戶(hù)代理字符串如下面的 4.79 版所示:

Mozilla/4.79 (Win98; I)

但是,微軟在發(fā)布 Internet Explorer 4 時(shí),順便將用戶(hù)代理字符串修改成了如下格式:

Mozilla/4.0 (compatible; MSIE 版本號(hào); 操作系統(tǒng))

換句話(huà)說(shuō),對(duì)于 Windows 98 中運(yùn)行的 IE4 而言,其用戶(hù)代理字符串為:

Mozilla/4.0 (compatible; MSIE 4.0; Windows 98)

經(jīng)過(guò)此番修改,Mozilla 版本號(hào)就與實(shí)際的 IE 版本號(hào)一致了,為識(shí)別它們的第四代瀏覽器提供了方

便。但令人遺憾的是,兩者的一致性?xún)H限于這一個(gè)版本。在 Internet Explorer 4.5 發(fā)布時(shí)(只針對(duì) Macs),

雖然 Mozilla 版本號(hào)還是 4,但 IE 版本號(hào)則改成了如下所示:

Mozilla/4.0 (compatible; MSIE 4.5; Mac_PowerPC)

此后,IE 的版本一直到 7 都沿襲了這個(gè)模式:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)

而 IE8 的用戶(hù)代理字符串中添加了呈現(xiàn)引擎(Trident)的版本號(hào):

Mozilla/4.0 (compatible; MSIE 版本號(hào); 操作系統(tǒng); Trident/Trident 版本號(hào))

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

第242頁(yè)

224 第 9 章 客戶(hù)端檢測(cè)

例如:

Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)

這個(gè)新增的 Trident 記號(hào)是為了讓開(kāi)發(fā)人員知道 IE8 是不是在兼容模式下運(yùn)行。如果是,則 MSIE 的

版本號(hào)會(huì)變成 7,但 Trident 及版本號(hào)還會(huì)留在用戶(hù)代碼字符串中:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0)

增加這個(gè)記號(hào)有助于分辨瀏覽器到底是 IE7(沒(méi)有 Trident 記號(hào)),還是運(yùn)行在兼容模式下的 IE8。

IE9 對(duì)字符串格式做了一點(diǎn)調(diào)整。Mozilla 版本號(hào)增加到了 5.0,而 Trident 的版本號(hào)也升到了 5.0。IE9

默認(rèn)的用戶(hù)代理字符串如下:

Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)

如果 IE9 運(yùn)行在兼容模式下,字符串中的 Mozilla 版本號(hào)和 MSIE 版本號(hào)會(huì)恢復(fù)舊的值,但 Trident

的版本號(hào)仍然是 5.0。例如,下面就是 IE9 運(yùn)行在 IE7 兼容模式下的用戶(hù)代理字符串:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0)

所有這些變化都是為了確保過(guò)去的用戶(hù)代理檢測(cè)腳本能夠繼續(xù)發(fā)揮作用,同時(shí)還能給新腳本提供更

豐富的信息。

4. Gecko

Gecko 是 Firefox 的呈現(xiàn)引擎。當(dāng)初的 Gecko 是作為通用 Mozilla 瀏覽器的一部分開(kāi)發(fā)的,而第一個(gè)

采用 Gecko 引擎的瀏覽器是 Netscape 6。為 Netscape 6 編寫(xiě)的一份規(guī)范中規(guī)定了未來(lái)版本中用戶(hù)代理字

符串的構(gòu)成。這個(gè)新格式與 4.x 版本中相對(duì)簡(jiǎn)單的字符串相比,有著非常大的區(qū)別,如下所示:

Mozilla/Mozilla 版本號(hào) (平臺(tái); 加密類(lèi)型; 操作系統(tǒng)或 CPU; 語(yǔ)言; 預(yù)先發(fā)行版本)

Gecko/Gecko 版本號(hào) 應(yīng)用程序或產(chǎn)品/應(yīng)用程序或產(chǎn)品版本號(hào)

這個(gè)明顯復(fù)雜了很多的用戶(hù)代理字符串中蘊(yùn)含很多新想法。下表列出了字符串中各項(xiàng)的用意。

字符串項(xiàng) 必需嗎 說(shuō) 明

Mozilla版本號(hào) 是 Mozilla的版本號(hào)

平臺(tái) 是 瀏覽器運(yùn)行的平臺(tái)??赡艿闹蛋╓indows、Mac和X11(指Unix的

X窗口系統(tǒng))

加密類(lèi)型 是 加密技術(shù)的類(lèi)型:U表示128位、I表示40位、N表示未加密

操作系統(tǒng)或CPU 是 瀏覽器運(yùn)行的操作系統(tǒng)或計(jì)算機(jī)系統(tǒng)使用的CPU。在Windows平臺(tái)

中,這一項(xiàng)指Windows的版本(如WinNT、Win95,等等)。如果平臺(tái)

是Macintosh,這一項(xiàng)指CPU(針對(duì)PowerPC的68K、PPC,或MacIntel)。

如果平臺(tái)是X11,這一項(xiàng)是Unix操作系統(tǒng)的名稱(chēng),與使用Unix命令

uname–sm得到的名稱(chēng)相同

語(yǔ)言 是 瀏覽器設(shè)計(jì)時(shí)所針對(duì)的目標(biāo)用戶(hù)語(yǔ)言

預(yù)先發(fā)行版本 否 最初用于表示Mozilla的預(yù)先發(fā)行版本,現(xiàn)在則用來(lái)表示Gecko呈現(xiàn)引

擎的版本號(hào)

Gecko版本號(hào) 是 Gecko呈現(xiàn)引擎的版本號(hào),但由yyyymmdd格式的日期表示

應(yīng)用程序或產(chǎn)品 否 使用Gecko的產(chǎn)品名??赡苁荖etscape、Firefox等

應(yīng)用程序或產(chǎn)品版本號(hào) 否 應(yīng)用程序或產(chǎn)品的版本號(hào);用于區(qū)分Mozilla版本號(hào)和Gecko版本號(hào)

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

第243頁(yè)

9.3 用戶(hù)代理檢測(cè) 225

1

2

3

4

5

13

6

7

8

9

10

11

12

為了幫助讀者更好地理解 Gecko 的用戶(hù)代理字符串,下面我們來(lái)看幾個(gè)從基于 Gecko 的瀏覽器中取

得的字符串。

Windows XP 下的 Netscape 6.21:

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:0.9.4) Gecko/20011128 Netscape6/6.2.1

Linux 下的 SeaMonkey 1.1a:

Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1b2) Gecko/20060823 SeaMonkey/1.1a

Windows XP 下的 Firefox 2.0.0.11:

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11

Mac OS X 下的 Camino 1.5.1:

Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en; rv:1.8.1.6) Gecko/20070809 Camino/1.5.1

以上這些用戶(hù)代理字符串都取自基于 Gecko 的瀏覽器(只是版本有所不同)。很多時(shí)候,檢測(cè)特定的

瀏覽器還不如搞清楚它是否基于 Gecko 更重要。每個(gè)字符串中的 Mozilla 版本都是 5.0,自從第一個(gè)基于

Gecko 的瀏覽器發(fā)布時(shí)修改成這個(gè)樣子,至今就沒(méi)有改變過(guò);而且,看起來(lái)以后似乎也不會(huì)有什么變化。

隨著 Firefox 4 發(fā)布,Mozilla 簡(jiǎn)化了這個(gè)用戶(hù)代理字符串。主要改變包括以下幾方面。

? 刪除了“語(yǔ)言”記號(hào)(例如,前面例子中的“en-US”)。

? 在瀏覽器使用強(qiáng)加密(默認(rèn)設(shè)置)時(shí),不顯示“加密類(lèi)型”。也就是說(shuō),Mozilla 用戶(hù)代理字符串

中不會(huì)再出現(xiàn)“U”,而“I”和“N”還會(huì)照常出現(xiàn)。

? “平臺(tái)”記號(hào)從 Windows 用戶(hù)代理字符串中刪除了,“操作系統(tǒng)或 CPU”中始終都包含

“Windows”字符串。

? “Gecko 版本號(hào)”固定為“Gecko/20100101”。

最后,F(xiàn)irefox 4 用戶(hù)代理字符串變成了下面這個(gè)樣子:

Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox 4.0.1

5. WebKit

2003 年,Apple 公司宣布要發(fā)布自己的 Web 瀏覽器,名字定為 Safari。Safari 的呈現(xiàn)引擎叫 WebKit,

是 Linux 平臺(tái)中 Konqueror 瀏覽器的呈現(xiàn)引擎 KHTML 的一個(gè)分支。幾年后,WebKit 獨(dú)立出來(lái)成為了一

個(gè)開(kāi)源項(xiàng)目,專(zhuān)注于呈現(xiàn)引擎的開(kāi)發(fā)。

這款新瀏覽器和呈現(xiàn)引擎的開(kāi)發(fā)人員也遇到了與 Internet Explorer 3.0 類(lèi)似的問(wèn)題:如何確保這款瀏

覽器不被流行的站點(diǎn)拒之門(mén)外?答案就是向用戶(hù)代理字符串中放入足夠多的信息,以便站點(diǎn)能夠信任它

與其他流行的瀏覽器是兼容的。于是,WebKit 的用戶(hù)代理字符串就具備了如下格式:

Mozilla/5.0 (平臺(tái); 加密類(lèi)型; 操作系統(tǒng)或 CPU; 語(yǔ)言) AppleWebKit/AppleWebKit 版本號(hào)

(KHTML, like Gecko) Safari/Safari 版本號(hào)

以下就是一個(gè)示例:

Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (KHTML, like Gecko)

Safari/125.1

顯然,這又是一個(gè)很長(zhǎng)的用戶(hù)代理字符串。其中不僅包含了 Apple WebKit 的版本號(hào),也包含了 Safari

的版本號(hào)。出于兼容性的考慮,有關(guān)人員很快就決定了將 Safari 標(biāo)識(shí)為 Mozilla。至今,基于 WebKit 的

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

第244頁(yè)

226 第 9 章 客戶(hù)端檢測(cè)

所有瀏覽器都將自己標(biāo)識(shí)為 Mozilla 5.0,與基于 Gecko 的瀏覽器完全一樣。但 Safari 的版本號(hào)則通常是

瀏覽器的編譯版本號(hào),不一定與發(fā)布時(shí)的版本號(hào)對(duì)應(yīng)。換句話(huà)說(shuō),雖然 Safari 1.25 的用戶(hù)代理字符串中

包含數(shù)字 125.1,但兩者卻不一一對(duì)應(yīng)。

Safari 預(yù)發(fā)行 1.0 版用戶(hù)代理字符串中最耐人尋味,也是最飽受詬病的部分就是字符串\"(KHTML,

like Gecko)\"。Apple 因此收到許多開(kāi)發(fā)人員的反饋,他們認(rèn)為這個(gè)字符串明顯是在欺騙客戶(hù)端和服

務(wù)器,實(shí)際上是想讓它們把 Safari 當(dāng)成 Gecko(好像光添加 Mozilla/5.0 還嫌不夠)。Apple 的回應(yīng)與

微軟在 IE 的用戶(hù)代理字符串遭到責(zé)難時(shí)如出一轍:Safari 與 Mozilla 兼容,因此網(wǎng)站不應(yīng)該將 Safari 用

戶(hù)拒之門(mén)外,否則用戶(hù)就會(huì)認(rèn)為自己的瀏覽器不受支持。

到了 Safari 3.0 發(fā)布時(shí),其用戶(hù)代理字符串又稍微變長(zhǎng)了一點(diǎn)。下面這個(gè)新增的 Version 記號(hào)一直到

現(xiàn)在都被用來(lái)標(biāo)識(shí) Safari 實(shí)際的版本號(hào):

Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/522.15.5 (KHTML, like

Gecko) Version/3.0.3 Safari/522.15.5

需要注意的是,這個(gè)變化只在 Safari 中有,在 WebKit 中沒(méi)有。換句話(huà)說(shuō),其他基于 WebKit 的瀏

覽器可能沒(méi)有這個(gè)變化。一般來(lái)說(shuō),確定瀏覽器是否基于 WebKit 要比確定它是不是 Safari 更有價(jià)值,

就像針對(duì) Gecko 一樣。

6. Konqueror

與 KDE Linux 集成的 Konqueror,是一款基于 KHTML 開(kāi)源呈現(xiàn)引擎的瀏覽器。盡管 Konqueror 只

能在 Linux 中使用,但它也有數(shù)量可觀的用戶(hù)。為確保最大限度的兼容性,Konqueror 效仿 IE 選擇了如

下用戶(hù)代理字符串格式:

Mozilla/5.0 (compatible; Konqueror/ 版本號(hào); 操作系統(tǒng)或 CPU )

不過(guò),為了與 WebKit 的用戶(hù)代理字符串的變化保持一致,Konqueror 3.2 又有了變化,以如下格式

將自己標(biāo)識(shí)為 KHTML:

Mozilla/5.0 (compatible; Konqueror/ 版本號(hào); 操作系統(tǒng)或 CPU) KHTML/ KHTML 版本號(hào) (like Gecko)

下面是一個(gè)例子:

Mozilla/5.0 (compatible; Konqueror/3.5; SunOS) KHTML/3.5.0 (like Gecko)

其中,Konqueror 與 KHTML 的版本號(hào)比較一致,即使有差別也很小,例如 Konqueror 3.5使用 KHTML

3.5.1。

7. Chrome

谷歌公司的 Chrome 瀏覽器以 WebKit 作為呈現(xiàn)引擎,但使用了不同的 JavaScript 引擎。在 Chrome 0.2

這個(gè)最初的 beta 版中,用戶(hù)代理字符串完全取自 WebKit,只添加了一段表示 Chrome 版本號(hào)的信息,格

式如下:

Mozilla/5.0 ( 平臺(tái); 加密類(lèi)型; 操作系統(tǒng)或 CPU; 語(yǔ)言) AppleWebKit/AppleWebKit 版本號(hào) (KHTML,

like Gecko) Chrome/ Chrome 版本號(hào) Safari/ Safari 版本

Chrome 7 的完整的用戶(hù)代理字符串如下:

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML,

like Gecko) Chrome/7.0.517.44 Safari/534.7

其中,WebKit 版本與 Safari 版本看起來(lái)似乎始終會(huì)保持一致,盡管沒(méi)有十分的把握。

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

第245頁(yè)

9.3 用戶(hù)代理檢測(cè) 227

1

2

3

4

5

13

6

7

8

9

10

11

12

8. Opera

僅就用戶(hù)代理字符串而言,Opera 應(yīng)該是最有爭(zhēng)議的一款瀏覽器了。Opera 默認(rèn)的用戶(hù)代理字符串

是所有現(xiàn)代瀏覽器中最合理的——正確地標(biāo)識(shí)了自身及其版本號(hào)。在 Opera 8.0 之前,其用戶(hù)代理字符

串采用如下格式:

Opera/ 版本號(hào) (操作系統(tǒng)或 CPU; 加密類(lèi)型) [語(yǔ)言]

Windows XP 中的 Opera 7.54 會(huì)顯示下面的用戶(hù)代理字符串:

Opera/7.54 (Windows NT 5.1; U) [en]

Opera 8 發(fā)布后,用戶(hù)代理字符串的“語(yǔ)言”部分被移到圓括號(hào)內(nèi),以便更好地與其他瀏覽器匹配,

如下所示:

Opera/ 版本號(hào) (操作系統(tǒng)或 CPU; 加密類(lèi)型; 語(yǔ)言)

Windows XP 中的 Opera 8 會(huì)顯示下面的用戶(hù)代理字符串:

Opera/8.0 (Windows NT 5.1; U; en)

默認(rèn)情況下,Opera 會(huì)以上面這種簡(jiǎn)單的格式返回一個(gè)用戶(hù)代理字符串。目前來(lái)看,Opera 也是主

要瀏覽器中唯一一個(gè)使用產(chǎn)品名和版本號(hào)來(lái)完全徹底地標(biāo)識(shí)自身的瀏覽器??墒?,與其他瀏覽器一樣,

Opera 在使用自己的用戶(hù)代理字符串時(shí)也遇到了問(wèn)題。即使技術(shù)上正確,但因特網(wǎng)上仍然有不少瀏覽器

嗅探代碼,只鐘情于報(bào)告 Mozilla 產(chǎn)品名的那些用戶(hù)代理字符串。另外還有相當(dāng)數(shù)量的代碼則只對(duì) IE 或

Gecko 感興趣。Opera 沒(méi)有選擇通過(guò)修改自身的用戶(hù)代理字符串來(lái)迷惑嗅探代碼,而是干脆選擇通過(guò)修

改自身的用戶(hù)代理字符串將自身標(biāo)識(shí)為一個(gè)完全不同的瀏覽器。

Opera 9 以后,出現(xiàn)了兩種修改用戶(hù)代理字符串的方式。一種方式是將自身標(biāo)識(shí)為另外一個(gè)瀏覽器,

如 Firefox 或者 IE。在這種方式下,用戶(hù)代理字符串就如同 Firefox 或 IE 的用戶(hù)代理字符串一樣,只不

過(guò)末尾追加了字符串 Opera 及 Opera 的版本號(hào)。下面是一個(gè)例子:

Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50

第一個(gè)字符串將 Opera 9.5 標(biāo)識(shí)為 Firefox 2,同時(shí)帶有 Opera 版本信息。第二個(gè)字符串將 Opera 9.5

標(biāo)識(shí)為 IE6,也包含了 Opera 版本信息。這兩個(gè)用戶(hù)代理字符串可以通過(guò)針對(duì) Firefox 或 IE 的大多數(shù)測(cè)

試,不過(guò)還是為識(shí)別 Opera 留下了余地。

Opera 標(biāo)識(shí)自身的另一種方式,就是把自己裝扮成 Firefox 或 IE。在這種隱瞞真實(shí)身份的情況下,用

戶(hù)代理字符串實(shí)際上與其他瀏覽器返回的相同——既沒(méi)有 Opera 字樣,也不包含 Opera 版本信息。換

句話(huà)說(shuō),在啟用了身份隱瞞功能的情況下,無(wú)法將 Opera 和其他瀏覽器區(qū)別開(kāi)來(lái)。另外,由于 Opera 喜

歡在不告知用戶(hù)的情況下針對(duì)站點(diǎn)來(lái)設(shè)置用戶(hù)代理字符串,因此問(wèn)題就更復(fù)雜化了。例如,打開(kāi) My

Yahoo!站點(diǎn)(http://my.yahoo.com)會(huì)自動(dòng)導(dǎo)致 Opera 將自己裝扮成 Firefox。如此一來(lái),要想識(shí)別 Opera

就難上加難了。

在 Opera 7 以前的版本中,Opera 會(huì)解析 Windows 操作系統(tǒng)字符串的含義。例如,

Windows NT 5.1 實(shí)際上就是 Windows XP,因此 Opera 會(huì)在用戶(hù)代理字符串中包含

Windows XP 而非 Windows NT 5.1。為了與其他瀏覽器更兼容,Opera 7 開(kāi)始包含正式

的操作系統(tǒng)版本,而非解析后的版本。

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

第246頁(yè)

228 第 9 章 客戶(hù)端檢測(cè)

Opera 10 對(duì)代理字符串進(jìn)行了修改?,F(xiàn)在的格式是:

Opera/9.80 (操作系統(tǒng)或 CPU; 加密類(lèi)型; 語(yǔ)言) Presto/Presto 版本號(hào) Version/版本號(hào)

注意,初始的版本號(hào) Opera/9.80 是固定不變的。實(shí)際并沒(méi)有 Opera 9.8,但工程師們擔(dān)心寫(xiě)得不好的

瀏覽器嗅探腳本會(huì)將 Opera/10.0 錯(cuò)誤的解釋為 Opera 1,而不是 Opera 10。因此,Opera 10 又增加了 Presto

記號(hào)(Presto 是 Opera 的呈現(xiàn)引擎)和 Version 記號(hào),后者用以保存實(shí)際的版本號(hào)。以下是 Windows7 中

Opera 10.63 的用戶(hù)代理字符串:

Opera/9.80 (Windows NT 6.1; U; en) Presto/2.6.30 Version/10.63

9. iOS 和 Android

移動(dòng)操作系統(tǒng) iOS 和 Android 默認(rèn)的瀏覽器都基于 WebKit,而且都像它們的桌面版一樣,共享相同

的基本用戶(hù)代理字符串格式。iOS 設(shè)備的基本格式如下:

Mozilla/5.0 (平臺(tái); 加密類(lèi)型; 操作系統(tǒng)或 CPU like Mac OS X; 語(yǔ)言)

AppleWebKit/AppleWebKit 版本號(hào) (KHTML, like Gecko) Version/瀏覽器版本號(hào)

Mobile/移動(dòng)版本號(hào) Safari/Safari 版本號(hào)

注意用于輔助確定 Mac 操作系統(tǒng)的\"like Mac OS X\"和額外的 Mobile 記號(hào)。一般來(lái)說(shuō),Mobile

記號(hào)的版本號(hào)(移動(dòng)版本號(hào))沒(méi)什么用,主要是用來(lái)確定 WebKit 是移動(dòng)版,而非桌面版。而平臺(tái)則可

能是\"iPhone\"、\"iPod\"或\"iPad\"。例如:

Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)

AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16

在 iOS 3 之前,用戶(hù)代理字符串中不會(huì)出現(xiàn)操作系統(tǒng)版本號(hào)。

Android 瀏覽器中的默認(rèn)格式與 iOS 的格式相似,沒(méi)有移動(dòng)版本號(hào)(但有 Mobile 記號(hào))。例如:

Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91)

AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1

這是 Google Nexus One 手機(jī)的用戶(hù)代理字符串。不過(guò),其他 Android 設(shè)備的模式也一樣。

9.3.2 用戶(hù)代理字符串檢測(cè)技術(shù)

考慮到歷史原因以及現(xiàn)代瀏覽器中用戶(hù)代理字符串的使用方式,通過(guò)用戶(hù)代理字符串來(lái)檢測(cè)特定的

瀏覽器并不是一件輕松的事。因此,首先要確定的往往是你需要多么具體的瀏覽器信息。一般情況下,

知道呈現(xiàn)引擎和最低限度的版本就足以決定正確的操作方法了。例如,我們不推薦使用下列代碼:

if (isIE6 || isIE7) { //不推薦!!!

//代碼

}

這個(gè)例子是想要在瀏覽器為 IE6 或 IE7 時(shí)執(zhí)行相應(yīng)代碼。這種代碼其實(shí)是很脆弱的,因?yàn)樗罁?jù)

特定的版本來(lái)決定做什么。如果是 IE8 怎么辦呢?只要 IE 有新版本出來(lái),就必須更新這些代碼。不過(guò),

像下面這樣使用相對(duì)版本號(hào)則可以避免此問(wèn)題:

if (ieVer >=6){

//代碼

}

這個(gè)例子首先檢測(cè) IE 的版本號(hào)是否至少等于 6,如果是則執(zhí)行相應(yīng)操作。這樣就可以確保相應(yīng)的代

碼將來(lái)照樣能夠起作用。我們下面的瀏覽器檢測(cè)腳本就將本著這種思路來(lái)編寫(xiě)。

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

第247頁(yè)

9.3 用戶(hù)代理檢測(cè) 229

1

2

3

4

5

13

6

7

8

9

10

11

12

1. 識(shí)別呈現(xiàn)引擎

如前所述,確切知道瀏覽器的名字和版本號(hào)不如確切知道它使用的是什么呈現(xiàn)引擎。如果 Firefox、

Camino 和 Netscape 都使用相同版本的 Gecko,那它們一定支持相同的特性。類(lèi)似地,不管是什么瀏覽

器,只要它跟 Safari 3 使用的是同一個(gè)版本的 WebKit,那么該瀏覽器也就跟 Safari 3 具備同樣的功能。

因此,我們要編寫(xiě)的腳本將主要檢測(cè)五大呈現(xiàn)引擎:IE、Gecko、WebKit、KHTML 和 Opera。

為了不在全局作用域中添加多余的變量,我們將使用模塊增強(qiáng)模式來(lái)封裝檢測(cè)腳本。檢測(cè)腳本的基

本代碼結(jié)構(gòu)如下所示:

var client = function(){

var engine = {

//呈現(xiàn)引擎

ie: 0,

gecko: 0,

webkit: 0,

khtml: 0,

opera: 0,

//具體的版本號(hào)

ver: null

};

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

return {

engine : engine

};

}();

這里聲明了一個(gè)名為 client 的全局變量,用于保存相關(guān)信息。匿名函數(shù)內(nèi)部定義了一個(gè)局部變量

engine,它是一個(gè)包含默認(rèn)設(shè)置的對(duì)象字面量。在這個(gè)對(duì)象字面量中,每個(gè)呈現(xiàn)引擎都對(duì)應(yīng)著一個(gè)屬

性,屬性的值默認(rèn)為 0。如果檢測(cè)到了哪個(gè)呈現(xiàn)引擎,那么就以浮點(diǎn)數(shù)值形式將該引擎的版本號(hào)寫(xiě)入相

應(yīng)的屬性。而呈現(xiàn)引擎的完整版本(是一個(gè)字符串),則被寫(xiě)入 ver 屬性。作這樣的區(qū)分可以支持像下

面這樣編寫(xiě)代碼:

if (client.engine.ie) { //如果是 IE,client.ie 的值應(yīng)該大于 0

//針對(duì) IE 的代碼

} else if (client.engine.gecko > 1.5){

if (client.engine.ver == \"1.8.1\"){

//針對(duì)這個(gè)版本執(zhí)行某些操作

}

}

在檢測(cè)到一個(gè)呈現(xiàn)引擎之后,其 client.engine 中對(duì)應(yīng)的屬性將被設(shè)置為一個(gè)大于 0 的值,該值

可以轉(zhuǎn)換成布爾值 true。這樣,就可以在 if 語(yǔ)句中檢測(cè)相應(yīng)的屬性,以確定當(dāng)前使用的呈現(xiàn)引擎,連

具體的版本號(hào)都不必考慮。鑒于每個(gè)屬性都包含一個(gè)浮點(diǎn)數(shù)值,因此有可能丟失某些版本信息。例如,

將字符串\"1.8.1\"傳入 parseFloat()后會(huì)得到數(shù)值 1.8。不過(guò),在必要的時(shí)候可以檢測(cè) ver 屬性,該

屬性中會(huì)保存完整的版本信息。

要正確地識(shí)別呈現(xiàn)引擎,關(guān)鍵是檢測(cè)順序要正確。由于用戶(hù)代理字符串存在諸多不一致的地方,如

果檢測(cè)順序不對(duì),很可能會(huì)導(dǎo)致檢測(cè)結(jié)果不正確。為此,第一步就是識(shí)別 Opera,因?yàn)樗挠脩?hù)代理字

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

第248頁(yè)

230 第 9 章 客戶(hù)端檢測(cè)

符串有可能完全模仿其他瀏覽器。我們不相信 Opera,是因?yàn)椋ㄈ魏吻闆r下)其用戶(hù)代理字符串(都)

不會(huì)將自己標(biāo)識(shí)為 Opera。

要識(shí)別 Opera,必須得檢測(cè) window.opera 對(duì)象。Opera 5 及更高版本中都有這個(gè)對(duì)象,用以保存

與瀏覽器相關(guān)的標(biāo)識(shí)信息以及與瀏覽器直接交互。在 Opera 7.6 及更高版本中,調(diào)用 version()方法可

以返回一個(gè)表示瀏覽器版本的字符串,而這也是確定Opera版本號(hào)的最佳方式。要檢測(cè)更早版本的Opera,

可以直接檢查用戶(hù)代理字符串,因?yàn)槟切┌姹具€不支持隱瞞身份。不過(guò),2007 底 Opera 的最高版本已經(jīng)

是 9.5 了,所以不太可能有人還在使用 7.6 之前的版本。那么,檢測(cè)呈現(xiàn)引擎代碼的第一步,就是編寫(xiě)

如下代碼:

if (window.opera){

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

engine.opera = parseFloat(engine.ver);

}

這里,將版本的字符串表示保存在了 engine.ver 中,將浮點(diǎn)數(shù)值表示的版本保存在了

engine.opera 中。如果瀏覽器是 Opera,測(cè)試 window.opera 就會(huì)返回 true;否則,就要看看是其

他的什么瀏覽器了。

應(yīng)該放在第二位檢測(cè)的呈現(xiàn)引擎是 WebKit。因?yàn)?WebKit 的用戶(hù)代理字符串中包含\"Gecko\"和

\"KHTML\"這兩個(gè)子字符串,所以如果首先檢測(cè)它們,很可能會(huì)得出錯(cuò)誤的結(jié)論。

不過(guò),WebKit 的用戶(hù)代理字符串中的\"AppleWebKit\"是獨(dú)一無(wú)二的,因此檢測(cè)這個(gè)字符串最合適。

下面就是檢測(cè)該字符串的示例代碼:

var ua = navigator.userAgent;

if (window.opera){

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

engine.opera = parseFloat(engine.ver);

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

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

engine.webkit = parseFloat(engine.ver);

}

代碼首先將用戶(hù)代理字符串保存在變量 ua 中。然后通過(guò)正則表達(dá)式來(lái)測(cè)試其中是否包含字符串

\"AppleWebKit\",并使用捕獲組來(lái)取得版本號(hào)。由于實(shí)際的版本號(hào)中可能會(huì)包含數(shù)字、小數(shù)點(diǎn)和字母,

所以捕獲組中使用了表示非空格的特殊字符(\\S)。用戶(hù)代理字符串中的版本號(hào)與下一部分的分隔符是

一個(gè)空格,因此這個(gè)模式可以保證捕獲所有版本信息。test()方法基于用戶(hù)代理字符串運(yùn)行正則表達(dá)

式。如果返回 true,就將捕獲的版本號(hào)保存在 engine.ver 中,而將版本號(hào)的浮點(diǎn)表示保存在

engine.webkit 中。WebKit 版本與 Safari 版本的詳細(xì)對(duì)應(yīng)情況如下表所示。

Safari版本號(hào) 最低限度的WebKit版本號(hào) Safari版本號(hào) 最低限度的WebKit版本號(hào)

1.0至1.0.2 85.7 1.3 312.1

1.0.3 85.8.2 1.3.1 312.5

1.1至1.1.1 100 1.3.2 312.8

1.2.2 125.2 2.0 412

1.2.3 125.4 2.0.1 412.7

1.2.4 125.5.5 2.0.2 416.11

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

第249頁(yè)

9.3 用戶(hù)代理檢測(cè) 231

1

2

3

4

5

13

6

7

8

9

10

11

12

(續(xù))

Safari版本號(hào) 最低限度的WebKit版本號(hào) Safari版本號(hào) 最低限度的WebKit版本號(hào)

2.0.3 417.9 3.0.4 523.10

2.0.4 418.8 3.1 525

有時(shí)候,Safari 版本并不會(huì)與 WebKit 版本嚴(yán)格地一一對(duì)應(yīng),也可能會(huì)存在某些小

版本上的差異。這個(gè)表中只是列出了最可能的 WebKit 版本,但不保證精確。

接下來(lái)要測(cè)試的呈現(xiàn)引擎是 KHTML。同樣,KHTML 的用戶(hù)代理字符串中也包含\"Gecko\",因此

在排除 KHTML 之前,我們無(wú)法準(zhǔn)確檢測(cè)基于 Gecko 的瀏覽器。KHTML 的版本號(hào)與 WebKit 的版本號(hào)

在用戶(hù)代理字符串中的格式差不多,因此可以使用類(lèi)似的正則表達(dá)式。此外,由于 Konqueror 3.1 及更

早版本中不包含 KHTML 的版本,故而就要使用 Konqueror 的版本來(lái)代替。下面就是相應(yīng)的檢測(cè)代碼。

var ua = navigator.userAgent;

if (window.opera){

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

engine.opera = parseFloat(engine.ver);

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

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

engine.webkit = parseFloat(engine.ver);

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

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

engine.khtml = parseFloat(engine.ver);

}

與前面一樣,由于 KHTML 的版本號(hào)與后繼的標(biāo)記之間有一個(gè)空格,因此仍然要使用特殊的非空格

字符來(lái)取得與版本有關(guān)的所有字符。然后,將字符串形式的版本信息保存在 engine.ver 中,將浮點(diǎn)數(shù)

值形式的版本保存在 engin.khtml 中。如果 KHTML 不在用戶(hù)代理字符串中,那么就要匹配 Konqueror

后跟一個(gè)斜杠,再后跟不包含分號(hào)的所有字符。

在排除了 WebKit 和 KHTML 之后,就可以準(zhǔn)確地檢測(cè) Gecko 了。但是,在用戶(hù)代理字符串中,Gecko

的版本號(hào)不會(huì)出現(xiàn)在字符串\"Gecko\"的后面,而是會(huì)出現(xiàn)在字符串\"rv:\"的后面。這樣,我們就必須使

用一個(gè)比前面復(fù)雜一些的正則表達(dá)式,如下所示。

var ua = navigator.userAgent;

if (window.opera){

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

engine.opera = parseFloat(engine.ver);

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

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

engine.webkit = parseFloat(engine.ver);

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

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

engine.khtml = parseFloat(engine.ver);

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

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

engine.gecko = parseFloat(engine.ver);

}

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

第250頁(yè)

232 第 9 章 客戶(hù)端檢測(cè)

Gecko 的版本號(hào)位于字符串\"rv:\"與一個(gè)閉括號(hào)之間,因此為了提取出這個(gè)版本號(hào),正則表達(dá)式要

查找所有不是閉括號(hào)的字符,還要查找字符串\"Gecko/\"后跟 8 個(gè)數(shù)字。如果上述模式匹配,就提取出

版本號(hào)并將其保存在相應(yīng)的屬性中。Gecko 版本號(hào)與 Firefox 版本號(hào)的對(duì)應(yīng)關(guān)系如下表所示。

Firefox版本號(hào) 最低限度的Gecko版本號(hào) Firefox版本號(hào) 最低限度的Gecko版本號(hào)

1.0 1.7.5 3.5 1.9.1

1.5 1.8.0 3.6 1.9.2

2.0 1.8.1 4.0 2.0.0

3.0 1.9.0

與 Safari 跟 WebKit 一樣,F(xiàn)irefox 與 Gecko 的版本號(hào)也不一定嚴(yán)格對(duì)應(yīng)。

最后一個(gè)要檢測(cè)的呈現(xiàn)引擎就是 IE 了。IE 的版本號(hào)位于字符串\"MSIE\"的后面、一個(gè)分號(hào)的前面,

因此相應(yīng)的正則表達(dá)式非常簡(jiǎn)單,如下所示:

var ua = navigator.userAgent;

if (window.opera){

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

engine.opera = parseFloat(engine.ver);

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

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

engine.webkit = parseFloat(engine.ver);

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

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

engine.khtml = parseFloat(engine.ver);

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

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

engine.gecko = parseFloat(engine.ver);

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

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

engine.ie = parseFloat(engine.ver);

}

以上呈現(xiàn)引擎檢測(cè)腳本的最后一部分,就是在正則表達(dá)式中使用取反的字符類(lèi)來(lái)取得不是分號(hào)的所

有字符。IE 通常會(huì)保證以標(biāo)準(zhǔn)浮點(diǎn)數(shù)值形式給出其版本號(hào),但有時(shí)候也不一定。因此,取反的字符類(lèi)[^;]

可以確保取得多個(gè)小數(shù)點(diǎn)以及任何可能的字符。

2. 識(shí)別瀏覽器

大多數(shù)情況下,識(shí)別了瀏覽器的呈現(xiàn)引擎就足以為我們采取正確的操作提供依據(jù)了。可是,只有呈

現(xiàn)引擎還不能說(shuō)明存在所需的 JavaScript 功能。蘋(píng)果公司的 Safari 瀏覽器和谷歌公司的 Chrome 瀏覽器都

使用 WebKit 作為呈現(xiàn)引擎,但它們的 JavaScript 引擎卻不一樣。在這兩款瀏覽器中,client.webkit

都會(huì)返回非 0值,但僅知道這一點(diǎn)恐怕還不夠。對(duì)于它們,有必要像下面這樣為 client 對(duì)象再添加一些

新的屬性。

var client = function(){

var engine = {

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

百萬(wàn)用戶(hù)使用云展網(wǎng)進(jìn)行書(shū)冊(cè)翻頁(yè)效果制作,只要您有文檔,即可一鍵上傳,自動(dòng)生成鏈接和二維碼(獨(dú)立電子書(shū)),支持分享到微信和網(wǎng)站!
收藏
轉(zhuǎn)發(fā)
下載
免費(fèi)制作
其他案例
更多案例
免費(fèi)制作
x
{{item.desc}}
下載
{{item.title}}
{{toast}}