??瀏覽器提供商雖然在實(shí)現(xiàn)公共接口方面投入了很多精力,但結(jié)果仍然是每一種瀏覽器都有各自的長(zhǎng)處,也都有各自的缺點(diǎn)。即使是那些跨平臺(tái)的瀏覽器,雖然從技術(shù)上看版本相同,也照樣存在不一致性問(wèn)題。
??面對(duì)普遍存在的不一致性問(wèn)題,開(kāi)發(fā)人員要么采取遷就各方的 “最小公分母” 策略,要么(也是更常見(jiàn)的)就得利用各種客戶端檢測(cè)方法,來(lái)突破或者規(guī)避種種局限性。
??迄今為止,客戶端檢測(cè)仍然是 Web 開(kāi)發(fā)領(lǐng)域中一個(gè)飽受爭(zhēng)議的話題。一談到這個(gè)話題,人們總會(huì)不約而同地提到瀏覽器應(yīng)該支持一組最常用的公共功能。
??在理想狀態(tài)下,確實(shí)應(yīng)該如此。但是,在現(xiàn)實(shí)當(dāng)中,瀏覽器之間的差異以及不同瀏覽器的“怪癖”(quirk),多得簡(jiǎn)直不勝枚舉。
??因此,客戶端檢測(cè)除了是一種補(bǔ)救措施之外,更是一種行之有效的開(kāi)發(fā)策略。
??檢測(cè) Web 客戶端的手段很多,而且各有利弊。但最重要的還是要知道,不到萬(wàn)不得已,就不要使用客戶端檢測(cè)。只要能找到更通用的方法,就應(yīng)該優(yōu)先采用更通用的方法。
??一言以蔽之,先設(shè)計(jì)最通用的方案,然后再使用特定于瀏覽器的技術(shù)增強(qiáng)該方案。
1、能力檢測(cè)
??最常用也最為人們廣泛接受的客戶端檢測(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];
} 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è)特性也存在。示例:
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。
??我們知道,IE8 及之前版本確實(shí)不支持 window.innerWidth 屬性。但問(wèn)題是 document.all 存在也不一定表示瀏覽器就是 IE。實(shí)際上,也可能是 Opera;Opera 支持 document.all,也支持 window.innerWidth。
1.1、更可靠的能力檢測(cè)
??能力檢測(cè)對(duì)于想知道某個(gè)特性是否會(huì)按照適當(dāng)方式行事(而不僅僅是某個(gè)特性存在)非常有用。上一節(jié)中的例子利用類(lèi)型轉(zhuǎn)換來(lái)確定某個(gè)對(duì)象成員是否存在,但這樣你還是不知道該成員是不是你想要的。
??看下面的函數(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";
}
??這里的 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ù)作為屬性訪問(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ì)。
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ǔ)后訪問(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);
??上述例子展示了兩個(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)判斷用戶使用的是什么瀏覽器。
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)中??梢允褂萌缦麓a來(lái)檢測(cè)這種“怪癖”。
var hasDontEnumQuirk = function(){
var o = { toString : function(){} };
for (var prop in o){
if (prop == "toString"){
return false;
}
}
return true;
}();
??上述代碼通過(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);
}();
??如果瀏覽器存在這個(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)題。
3、用戶代理檢測(cè)
??第三種,也是爭(zhēng)議最大的一種客戶端檢測(cè)技術(shù)叫做用戶代理檢測(cè)。
??用戶代理檢測(cè)通過(guò)檢測(cè)用戶代理字符串來(lái)確定實(shí)際使用的瀏覽器。在每一次 HTTP 請(qǐng)求過(guò)程中,用戶代理字符串是作為響應(yīng)首部發(fā)送的,而且該字符串可以通過(guò) JavaScript 的 navigator.userAgent 屬性訪問(wèn)。
??在服務(wù)器端,通過(guò)檢測(cè)用戶代理字符串來(lái)確定用戶使用的瀏覽器是一種常用而且廣為接受的做法。而在客戶端,用戶代理檢測(cè)一般被當(dāng)作一種萬(wàn)不得已才用的做法,其優(yōu)先級(jí)排在能力檢測(cè)和(或)怪癖檢測(cè)之后。
??提到與用戶代理字符串有關(guān)的爭(zhēng)議,就不得不提到電子欺騙(spoofing)。
??所謂電子欺騙,就是指瀏覽器通過(guò)在自己的用戶代理字符串加入一些錯(cuò)誤或誤導(dǎo)性信息,來(lái)達(dá)到欺騙服務(wù)器的目的。要弄清楚這個(gè)問(wèn)題的來(lái)龍去脈,必須從 Web 問(wèn)世初期用戶代理字符串的發(fā)展講起。
3.1、用戶代理字符串的歷史
??HTTP 規(guī)范(包括 1.0 和 1.1 版)明確規(guī)定,瀏覽器應(yīng)該發(fā)送簡(jiǎn)短的用戶代理字符串,指明瀏覽器的名稱(chēng)和版本號(hào)。RFC 2616(即 HTTP 1.1 協(xié)議規(guī)范)是這樣描述用戶代理字符串的:
????“產(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)用程序。”
??上述規(guī)范進(jìn)一步規(guī)定,用戶代理字符串應(yīng)該以一組產(chǎn)品的形式給出,字符串格式為:標(biāo)識(shí)符/產(chǎn)品版本號(hào)。
??但是,現(xiàn)實(shí)中的用戶代理字符串則絕沒(méi)有如此簡(jiǎn)單。
3.2、用戶代理字符串檢測(cè)技術(shù)
??考慮到歷史原因以及現(xiàn)代瀏覽器中用戶代理字符串的使用方式,通過(guò)用戶代理字符串來(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ě)。
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è)順序要正確。由于用戶代理字符串存在諸多不一致的地方,如果檢測(cè)順序不對(duì),很可能會(huì)導(dǎo)致檢測(cè)結(jié)果不正確。
??為此,第一步就是識(shí)別 Opera,因?yàn)樗挠脩舸碜址锌赡芡耆7缕渌麨g覽器。我們不相信 Opera,是因?yàn)椋ㄈ魏吻闆r下)其用戶代理字符串(都)不會(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,可以直接檢查用戶代理字符串,因?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 的用戶代理字符串中包含 "Gecko" 和 "KHTML" 這兩個(gè)子字符串,所以如果首先檢測(cè)它們,很可能會(huì)得出錯(cuò)誤的結(jié)論。
??不過(guò),WebKit 的用戶代理字符串中的 "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);
}
??代碼首先將用戶代理字符串保存在變量 ua 中。然后通過(guò)正則表達(dá)式來(lái)測(cè)試其中是否包含字符串 "AppleWebKit",并使用捕獲組來(lái)取得版本號(hào)。
??由于實(shí)際的版本號(hào)中可能會(huì)包含數(shù)字、小數(shù)點(diǎn)和字母,
所以捕獲組中使用了表示非空格的特殊字符(\S)。用戶代理字符串中的版本號(hào)與下一部分的分隔符是一個(gè)空格,因此這個(gè)模式可以保證捕獲所有版本信息。
??test() 方法基于用戶代理字符串運(yùn)行正則表達(dá)式。如果返回 true,就將捕獲的版本號(hào)保存在 engine.ver 中,而將版本號(hào)的浮點(diǎn)表示保存在engine.webkit 中。
??接下來(lái)要測(cè)試的呈現(xiàn)引擎是 KHTML。同樣,KHTML 的用戶代理字符串中也包含 "Gecko",因此在排除 KHTML 之前,我們無(wú)法準(zhǔn)確檢測(cè)基于 Gecko 的瀏覽器。
??KHTML 的版本號(hào)與 WebKit 的版本號(hào)在用戶代理字符串中的格式差不多,因此可以使用類(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 不在用戶代理字符串中,那么就要匹配 Konqueror 后跟一個(gè)斜杠,再后跟不包含分號(hào)的所有字符。
??在排除了 WebKit 和 KHTML 之后,就可以準(zhǔn)確地檢測(cè) Gecko 了。但是,在用戶代理字符串中,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);
}
??Gecko 的版本號(hào)位于字符串 "rv:" 與一個(gè)閉括號(hào)之間,因此為了提取出這個(gè)版本號(hào),正則表達(dá)式要查找所有不是閉括號(hào)的字符,還要查找字符串 "Gecko/" 后跟 8 個(gè)數(shù)字。如果上述模式匹配,就提取出
版本號(hào)并將其保存在相應(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 = {
// 呈現(xiàn)引擎
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
// 具體的版本
ver: null
};
var browser = {
// 瀏覽器
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
// 具體的版本
ver: null
};
// 在此檢測(cè)呈現(xiàn)引擎、平臺(tái)和設(shè)備
return {
engine: engine,
browser: browser
};
}();
??代碼中又添加了私有變量 browser,用于保存每個(gè)主要瀏覽器的屬性。
??與 engine 變量一樣,除了當(dāng)前使用的瀏覽器,其他屬性的值將保持為 0;如果是當(dāng)前使用的瀏覽器,則這個(gè)屬性中保存的是浮點(diǎn)數(shù)值形式的版本號(hào)。
??同樣,ver 屬性中在必要時(shí)將會(huì)包含字符串形式的瀏覽器完整版本號(hào)。由于大多數(shù)瀏覽器與其呈現(xiàn)引擎密切相關(guān),所以下面示例中檢測(cè)瀏覽器的代碼與檢測(cè)呈現(xiàn)引擎的代碼是混合在一起的。
// 檢測(cè)呈現(xiàn)引擎及瀏覽器
var ua = navigator.userAgent;
if (window.opera){
engine.ver = browser.ver = window.opera.version();
engine.opera = browser.opera = parseFloat(engine.ver);
} else if (/AppleWebKit\/(\S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
// 確定是 Chrome 還是 Safari
if (/Chrome\/(\S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.chrome = parseFloat(browser.ver);
} else if (/Version\/(\S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.safari = parseFloat(browser.ver);
} else {
// 近似地確定版本號(hào)
var safariVersion = 1;
if (engine.webkit < 100){
safariVersion = 1;
} else if (engine.webkit < 312){
safariVersion = 1.2;
} else if (engine.webkit < 412){
safariVersion = 1.3;
} else {
safariVersion = 2;
}
browser.safari = browser.ver = safariVersion;
}
} else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
engine.ver = browser.ver = RegExp["$1"];
engine.khtml = browser.konq = parseFloat(engine.ver);
} else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){
engine.ver = RegExp["$1"];
engine.gecko = parseFloat(engine.ver);
// 確定是不是 Firefox
if (/Firefox\/(\S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.firefox = parseFloat(browser.ver);
}
} else if (/MSIE ([^;]+)/.test(ua)){
engine.ver = browser.ver = RegExp["$1"];
engine.ie = browser.ie = parseFloat(engine.ver);
}
??對(duì) Opera 和 IE 而言,browser 對(duì)象中的值等于 engine 對(duì)象中的值。
??對(duì) Konqueror 而言,browser.konq 和 browser.ver 屬性分別等于 engine.khtml 和 engine.ver 屬性。
??為了檢測(cè) Chrome 和 Safari,我們?cè)跈z測(cè)引擎的代碼中添加了 if 語(yǔ)句。提取 Chrome 的版本號(hào)時(shí),需要查找字符串 "Chrome/" 并取得該字符串后面的數(shù)值。而提取 Safari 的版本號(hào)時(shí),則需要查找字符串 "Version/" 并取得其后的數(shù)值。
??由于這種方式僅適用于 Safari 3 及更高版本,因此需要一些備用的代碼,將 WebKit 的版本號(hào)近似地映射為 Safari 的版本號(hào)。
??在檢測(cè) Firefox 的版本時(shí),首先要找到字符串 "Firefox/",然后提取出該字符串后面的數(shù)值(即版本號(hào))。當(dāng)然,只有呈現(xiàn)引擎被判別為 Gecko 時(shí)才會(huì)這樣做。
??有了上面這些代碼之后,我們就可以編寫(xiě)下面的邏輯。
if (client.engine.webkit) { // if it’s WebKit
if (client.browser.chrome){
// 執(zhí)行針對(duì) Chrome 的代碼
} else if (client.browser.safari){
// 執(zhí)行針對(duì) Safari 的代碼
}
} else if (client.engine.gecko){
if (client.browser.firefox){
// 執(zhí)行針對(duì) Firefox 的代碼
} else {
// 執(zhí)行針對(duì)其他 Gecko 瀏覽器的代碼
}
}
3. 識(shí)別平臺(tái)
??很多時(shí)候,只要知道呈現(xiàn)引擎就足以編寫(xiě)出適當(dāng)?shù)拇a了。但在某些條件下,平臺(tái)可能是必須關(guān)注的問(wèn)題。
??那些具有各種平臺(tái)版本的瀏覽器(如 Safari、Firefox 和 Opera)在不同的平臺(tái)下可能會(huì)有不同的問(wèn)題。
??目前的三大主流平臺(tái)是 Windows、Mac 和 Unix(包括各種 Linux)。為了檢測(cè)這些平臺(tái),還需要像下面這樣再添加一個(gè)新對(duì)象。
var client = function(){
var engine = {
// 呈現(xiàn)引擎
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
// 具體的版本
ver: null
};
var browser = {
// 瀏覽器
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
// 具體的版本
ver: null
};
var system = {
win: false,
mac: false,
x11: false
};
// 在此檢測(cè)呈現(xiàn)引擎、平臺(tái)和設(shè)備
return {
engine: engine,
browser: browser,
system: system
};
}();
??顯然,上面的代碼中又添加了一個(gè)包含 3 個(gè)屬性的新變量 system。
??其中,win 屬性表示是否為Windows 平臺(tái),mac 表示 Mac,而 x11 表示 Unix。
??與呈現(xiàn)引擎不同,在不能訪問(wèn)操作系統(tǒng)或版本的情況下,平臺(tái)信息通常是很有限的。對(duì)這三個(gè)平臺(tái)而言,瀏覽器一般只報(bào)告 Windows 版本。
??為此,新變量 system 的每個(gè)屬性最初都保存著布爾值 false,而不是像呈現(xiàn)引擎屬性那樣保存著數(shù)字值。在確定平臺(tái)時(shí),檢測(cè) navigator.platform 要比檢測(cè)用戶代理字符串更簡(jiǎn)單,后者在不同瀏覽器
中會(huì)給出不同的平臺(tái)信息。而 navigator.platform 屬性可能的值包括 "Win32"、"Win64"、"MacPPC"、"MacIntel"、"X11" 和 "Linux i686",這些值在不同的瀏覽器中都是一致的。
??檢測(cè)平臺(tái)的代碼非常直觀,如下所示:
var p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = (p.indexOf("X11") == 0) || (p.indexOf("Linux") == 0);
??以上代碼使用 indexOf() 方法來(lái)查找平臺(tái)字符串的開(kāi)始位置。雖然 "Win32" 是當(dāng)前瀏覽器唯一支持的 Windows 字符串,但隨著向 64 位 Windows 架構(gòu)的遷移,將來(lái)很可能會(huì)出現(xiàn) "Win64" 平臺(tái)信息值。
??為了對(duì)此有所準(zhǔn)備,檢測(cè)平臺(tái)的代碼中查找的只是字符串 "Win" 的開(kāi)始位置。
??而檢測(cè) Mac 平臺(tái)的方式也類(lèi)似,同樣是考慮到了 MacPPC 和 MacIntel。
??在檢測(cè) Unix 時(shí),則同時(shí)檢查了字符串"X11"和"Linux" 在平臺(tái)字符串中的開(kāi)始位置,從而確保了代碼能夠向前兼容其他變體。
4. 識(shí)別 Windows 操作系統(tǒng)
??正則表達(dá)式可以成功地匹配 Windows ME、Windows XP 和 Windows Vista 的字符串。具體來(lái)說(shuō),第一個(gè)捕獲組將會(huì)匹配 95、98、9x、NT、ME 或 XP。第二個(gè)捕獲組則只針對(duì) Windows ME 及所有 Windows NT 的變體。這個(gè)信息可以作為具體的操作系統(tǒng)信息保存在 system.win屬性中,如下所示。
if (system.win){
if (/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)){
if (RegExp["$1"] == "NT"){
switch(RegExp["$2"]){
case "5.0":
system.win = "2000";
break;
case "5.1":
system.win = "XP";
break;
case "6.0":
system.win = "Vista";
break;
case "6.1":
system.win = "7";
break;
default:
system.win = "NT";
break;
}
} else if (RegExp["$1"] == "9x"){
system.win = "ME";
} else {
system.win = RegExp["$1"];
}
}
}
??如果 system.win 的值為 true,那么就使用這個(gè)正則表達(dá)式從用戶代理字符串中提取具體的信息。
??鑒于 Windows 將來(lái)的某個(gè)版本也許不能使用這個(gè)方法來(lái)檢測(cè),所以第一步應(yīng)該先檢測(cè)用戶代理字符串是否與這個(gè)模式匹配。
??在模式匹配的情況下,第一個(gè)捕獲組中可能會(huì)包含"95"、"98"、"9x" 或 "NT"。如果這個(gè)值是 "NT",可以將 system.win 設(shè)置為相應(yīng)操作系統(tǒng)的字符串;如果是 "9x",那么 system.win就要設(shè)置成 "ME";如果是其他值,則將所捕獲的值直接賦給 system.win。有了這些檢測(cè)平臺(tái)的代碼后,我們就可以編寫(xiě)如下代碼。
if (client.system.win){
if (client.system.win == "XP") {
// 說(shuō)明是 XP
} else if (client.system.win == "Vista"){
// 說(shuō)明是 Vista
}
}
??由于非空字符串會(huì)轉(zhuǎn)換為布爾值 true,因此可以將 client.system.win 作為布爾值用在 if 語(yǔ)句中。而在需要更多有關(guān)操作系統(tǒng)的信息時(shí),則可以使用其中保存的字符串值。
5. 識(shí)別移動(dòng)設(shè)備
??四大主要瀏覽器都推出了手機(jī)版和在其他設(shè)備中運(yùn)行的版本。要檢測(cè)相應(yīng)的設(shè)備,第一步是為要檢測(cè)的所有移動(dòng)設(shè)備添加屬性,如
下所示。
var client = function(){
var engine = {
// 呈現(xiàn)引擎
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
// 具體的版本
ver: null
};
var browser = {
// 瀏覽器
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
// 具體的版本
ver: null
};
var system = {
win: false,
mac: false,
x11: false,
// 移動(dòng)設(shè)備
iphone: false,
ipod: false,
ipad: false,
ios: false,
android: false,
nokiaN: false,
winMobile: false
};
// 在此檢測(cè)呈現(xiàn)引擎、平臺(tái)和設(shè)備
return {
engine: engine,
browser: browser,
system: system
};
}();
??然后,通常簡(jiǎn)單地檢測(cè)字符串 "iPhone"、 "iPod" 和 "iPad",就可以分別設(shè)置相應(yīng)屬性的值了。
system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipod = ua.indexOf("iPad") > -1;
6. 識(shí)別游戲系統(tǒng)
??除了移動(dòng)設(shè)備之外,視頻游戲系統(tǒng)中的 Web 瀏覽器也開(kāi)始日益普及。任天堂 Wii 和 Playstation 3 或者內(nèi)置 Web 瀏覽器,或者提供了瀏覽器下載。Wii 中的瀏覽器實(shí)際上是定制版的 Opera,是專(zhuān)門(mén)為 Wii
Remote 設(shè)計(jì)的。Playstation 的瀏覽器是自己開(kāi)發(fā)的,沒(méi)有基于前面提到的任何呈現(xiàn)引擎。這兩個(gè)瀏覽器中的用戶代理字符串如下所示:
Opera/9.10 (Nintendo Wii;U; ; 1621; en)
Mozilla/5.0 (PLAYSTATION 3; 2.00)
??在檢測(cè)這些設(shè)備以前,我們必須先為 client.system 中添加適當(dāng)?shù)膶傩?,如下所示?/p>
var client = function(){
var engine = {
// 呈現(xiàn)引擎
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
// 具體的版本
ver: null
};
var browser = {
// 瀏覽器
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
// 具體的版本
ver: null
};
var system = {
win: false,
mac: false,
x11: false,
// 移動(dòng)設(shè)備
iphone: false,
ipod: false,
ipad: false,
ios: false,
android: false,
nokiaN: false,
winMobile: false
//游戲系統(tǒng)
wii: false,
ps: false
};
// 在此檢測(cè)呈現(xiàn)引擎、平臺(tái)和設(shè)備
return {
engine: engine,
browser: browser,
system: system
};
}();
??檢測(cè)前述游戲系統(tǒng)的代碼如下:
system.wii = ua.indexOf("Wii") > -1;
system.ps = /playstation/i.test(ua);
3.3、完整的代碼
??以下是完整的用戶代理字符串檢測(cè)腳本,包括檢測(cè)呈現(xiàn)引擎、平臺(tái)、Windows 操作系統(tǒng)、移動(dòng)設(shè)備和游戲系統(tǒng)。
var client = function(){
// 呈現(xiàn)引擎
var engine = {
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
// 完整的版本號(hào)
ver: null
};
// 瀏覽器
var browser = {
// 主要瀏覽器
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
// 具體的版本號(hào)
ver: null
};
// 平臺(tái)、設(shè)備和操作系統(tǒng)]
var system = {
win: false,
mac: false,
x11: false,
// 移動(dòng)設(shè)備
iphone: false,
ipod: false,
ipad: false,
ios: false,
android: false,
nokiaN: false,
winMobile: false
//游戲系統(tǒng)
wii: false,
ps: false
};
// 檢測(cè)呈現(xiàn)引擎和瀏覽器
var ua = navigator.userAgent;
if (window.opera){
engine.ver = browser.ver = window.opera.version();
engine.opera = browser.opera = parseFloat(engine.ver);
} else if (/AppleWebKit\/(\S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
// 確定是 Chrome 還是 Safari
if (/Chrome\/(\S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.chrome = parseFloat(browser.ver);
} else if (/Version\/(\S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.safari = parseFloat(browser.ver);
} else {
// 近似地確定版本號(hào)
var safariVersion = 1;
if (engine.webkit < 100){
safariVersion = 1;
} else if (engine.webkit < 312){
safariVersion = 1.2;
} else if (engine.webkit < 412){
safariVersion = 1.3;
} else {
safariVersion = 2;
}
browser.safari = browser.ver = safariVersion;
}
} else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
engine.ver = browser.ver = RegExp["$1"];
engine.khtml = browser.konq = parseFloat(engine.ver);
} else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){
engine.ver = RegExp["$1"];
engine.gecko = parseFloat(engine.ver);
// 確定是不是 Firefox
if (/Firefox\/(\S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.firefox = parseFloat(browser.ver);
}
} else if (/MSIE ([^;]+)/.test(ua)){
engine.ver = browser.ver = RegExp["$1"];
engine.ie = browser.ie = parseFloat(engine.ver);
}
// 檢測(cè)瀏覽器
browser.ie = engine.ie;
browser.opera = engine.opera;
// 檢測(cè)平臺(tái)
var p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);
// 檢測(cè) Windows 操作系統(tǒng)
if (system.win){
if (/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)){
if (RegExp["$1"] == "NT"){
switch(RegExp["$2"]){
case " 5.0":
system.win = "2000";
break;
case "5.1":
system.win = "XP";
break;
case "6.0":
system.win = "Vista";
break;
case "6.1":
system.win = "7";
break;
default:
system.win = "NT";
break;
}
} else if (RegExp["$1"] == "9x"){
system.win = "ME";
} else {
system.win = RegExp["$1"];
}
}
}
// 移動(dòng)設(shè)備
system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipad = ua.indexOf("iPad") > -1;
system.nokiaN = ua.indexOf("NokiaN") > -1;
// windows mobile
if (system.win == "CE"){
system.winMobile = system.win;
} else if (system.win == "Ph"){
if(/Windows Phone OS (\d+.\d+)/.test(ua)){;
system.win = "Phone";
system.winMobile = parseFloat(RegExp["$1"]);
}
}
// 檢測(cè) iOS 版本
if (system.mac && ua.indexOf("Mobile") > -1){
if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)){
system.ios = parseFloat(RegExp.$1.replace("_", "."));
} else {
system.ios = 2; //不能真正檢測(cè)出來(lái),所以只能猜測(cè)
}
}
// 檢測(cè) Android 版本
if (/Android (\d+\.\d+)/.test(ua)){
system.android = parseFloat(RegExp.$1);
}
// 游戲系統(tǒng)
system.wii = ua.indexOf("Wii") > -1;
system.ps = /playstation/i.test(ua);
// 返回這些對(duì)象
return {
engine: engine,
browser: browser,
system: system
};
}();
3.4、使用方法
??我們?cè)谇懊嬉呀?jīng)強(qiáng)調(diào)過(guò)了,用戶代理檢測(cè)是客戶端檢測(cè)的最后一個(gè)選擇。只要可能,都應(yīng)該優(yōu)先采用能力檢測(cè)和怪癖檢測(cè)。
??用戶代理檢測(cè)一般適用于下列情形。
- 不能直接準(zhǔn)確地使用能力檢測(cè)或怪癖檢測(cè)。例如,某些瀏覽器實(shí)現(xiàn)了為將來(lái)功能預(yù)留的存根(stub)函數(shù)。在這種情況下,僅測(cè)試相應(yīng)的函數(shù)是否存在還得不到足夠的信息。
- 同一款瀏覽器在不同平臺(tái)下具備不同的能力。這時(shí)候,可能就有必要確定瀏覽器位于哪個(gè)平臺(tái)下。
- 為了跟蹤分析等目的需要知道確切的瀏覽器。
小結(jié)
??客戶端檢測(cè)是 JavaScript 開(kāi)發(fā)中最具爭(zhēng)議的一個(gè)話題。由于瀏覽器間存在差別,通常需要根據(jù)不同瀏覽器的能力分別編寫(xiě)不同的代碼。
??有不少客戶端檢測(cè)方法,但下列是最經(jīng)常使用的。
- 能力檢測(cè):在編寫(xiě)代碼之前先檢測(cè)特定瀏覽器的能力。例如,腳本在調(diào)用某個(gè)函數(shù)之前,可能要先檢測(cè)該函數(shù)是否存在。這種檢測(cè)方法將開(kāi)發(fā)人員從考慮具體的瀏覽器類(lèi)型和版本中解放出來(lái),讓他們把注意力集中到相應(yīng)的能力是否存在上。能力檢測(cè)無(wú)法精確地檢測(cè)特定的瀏覽器和版本。
-
怪癖檢測(cè):怪癖實(shí)際上是瀏覽器實(shí)現(xiàn)中存在的 bug,例如早期的 WebKit 中就存在一個(gè)怪癖,即它會(huì)在 for-in 循環(huán)中返回被隱藏的屬性。怪癖檢測(cè)通常涉及到運(yùn)行一小段代碼,然后確定瀏覽器是否存在某個(gè)怪癖。由于怪癖檢測(cè)與能力檢測(cè)相比效率更低,因此應(yīng)該只在某個(gè)怪癖會(huì)干
擾腳本運(yùn)行的情況下使用。怪癖檢測(cè)無(wú)法精確地檢測(cè)特定的瀏覽器和版本。 - 用戶代理檢測(cè):通過(guò)檢測(cè)用戶代理字符串來(lái)識(shí)別瀏覽器。用戶代理字符串中包含大量與瀏覽器有關(guān)的信息,包括瀏覽器、平臺(tái)、操作系統(tǒng)及瀏覽器版本。用戶代理字符串有過(guò)一段相當(dāng)長(zhǎng)的發(fā)展歷史,在此期間,瀏覽器提供商試圖通過(guò)在用戶代理字符串中添加一些欺騙性信息,欺騙網(wǎng)站相信自己的瀏覽器是另外一種瀏覽器。用戶代理檢測(cè)需要特殊的技巧,特別是要注意 Opera 會(huì)隱瞞其用戶代理字符串的情況。即便如此,通過(guò)用戶代理字符串仍然能夠檢測(cè)出瀏覽器所用的呈現(xiàn)引擎以及所在的平臺(tái),包括移動(dòng)設(shè)備和游戲系統(tǒng)。
??在決定使用哪種客戶端檢測(cè)方法時(shí),一般應(yīng)優(yōu)先考慮使用能力檢測(cè)。怪癖檢測(cè)是確定應(yīng)該如何處理代碼的第二選擇。而用戶代理檢測(cè)則是客戶端檢測(cè)的最后一種方案,因?yàn)檫@種方法對(duì)用戶代理字符串具
有很強(qiáng)的依賴(lài)性。