檢測 Web 客戶端的手段很多,而且各有利弊,但最重要的是,不到萬不得已,就不要使用客戶端檢測。
只要能夠找到更通用的方法,就應該優先采用更通用的方法。
一言而蔽之,先設計最通用的方案,然后再使用特定於瀏覽器的技術增強該方案。
一、能力檢測
最常用也最為人們廣泛接受的客戶端檢測形式是能力檢測(又稱特性檢測)。能力檢測的目標不是識別特定的瀏覽器,而是識別瀏覽器的能力。采用這種方式不必顧忌特定的瀏覽器如何,只要確定瀏覽器支持特定的能力,就可以給出解決方案。
能力檢測的基本模式如下:
if( object.propertyInQuestion){ //使用 object.propertyInQuestion }
兩個重要的概念:
第一個概念是先檢測達成目的的最常用的特性。先檢測最常用的特性,可以保證代碼最優化,因為在多數情況下都可以避免測試多個條件。
第二個概念是必須測試實際要用到的特性。一個特性存在,不一定意味着另一個特性也存在。
檢測某個或某幾個特性並不能夠確定瀏覽器。實際上,根據瀏覽器不同將能力組合起來是更可取的方式。
如果知道自己的應用程序需要使用某些特定的瀏覽器特性,那么最好是一次性檢測所有相關特性,而不要分別檢測。
//確定瀏覽器是否支持 Netscape 風格的插件 var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length ); //確定瀏覽器是否具有 DOM1 級規定的能力 var hasDOM1 = !!(document.getElementById && document.createElement && document.getElementByTagName);
以上兩個例子,一個檢測是否支持 Netscape 風格的產檢;另一個檢測瀏覽器是否具備DOM1 級所規定的能力。所得到的布爾值可以在以后繼續使用,從而節省重新檢測能力的時間。
在實際開發中,應該將能力檢測作為確定下一步解決方案的依據,而不是用它來判斷用戶使用的是什么瀏覽器。
二、怪癖檢測
與能力檢測類似,怪癖檢測(quirks detection)的目標是識別瀏覽器的特殊行為。
但與能力檢測不同,怪癖檢測是想要知道瀏覽器存在什么缺陷。這個通常要運行一小段代碼,以確定某一特性不能正常工作。
例如,IE中存在的一個 bug ,即如果某個實例屬性與標記為 [[DontEnum]] 的某個原型屬性同名,那么該實例屬性將不會出現在 fon-in 循環當中。可以使用如下代碼來檢測這種“怪癖”:
var hasDontEnumQuirk = function(){ var o = { toString : function(){}}; for( var prop in o){ if( prop == "toString"){ return false; } } return true; }();
以上代碼通過一個匿名函數來測試該“怪癖”,函數中創建了一個帶有 toString() 方法的對象。在正確的 ECMAScript 實現中,toString 應該在 for-in 循環中作為屬性返回。
另一個經常要檢測的"怪癖"是safari 3.0 以前版本會美劇被隱藏的屬性。可以用一下函數來檢測:
var hasEnumShadownsQuirk = function(){ var o = { toString : functionn(){} }; var count = 0; for( var prop in o){ if( prop == "toString"){ count++; } } return (count > 1); }();
如果瀏覽器存在這個bug,那么使用 for-in 循環枚舉帶有自定義的toString()方法的對象,就會返回兩個 toString 的實例。
一般來說,“怪癖”都是個別瀏覽器所獨有的,而且通常被歸為 bug。
在相關瀏覽器的信版本中,這些問題可能會也可能不會被修復。由於檢測“怪癖”涉及運行代碼,因此建議僅檢測那些對你有直接影響的“怪癖”,而且最好在腳本一開始就執行此類檢測,以便盡早解決問題。
三、用戶代理檢測
第三種,也是爭議最大的一種客戶端檢測技術叫用戶代理檢測。
用戶代理檢測通過檢測用戶代理字符串來確定實際使用的瀏覽器。
在每一次HTTP請求過程中,用戶代理字符串是作為響應首部發送的,而該字符串可以通過 JavaScript 的 navigator.userAgent 屬性訪問。
在服務器端,通過檢測用戶代理字符串來確定用戶使用的瀏覽器是一種常用而且廣為接受的做法。
而在客戶端,用戶代理檢測一般被當作一種萬不得已才使用的做法,其優先級排在能力檢測和怪癖檢測之后。
一、用戶代理字符串檢測技術
1)識別呈現引擎
確切的知道瀏覽器的名字和版本號不如確定他們使用的是什么呈現引擎。如果Firefox、Camino 和 Netsacpe 都使用相同版本的 Gecko ,那么他們一定支持相同的特性。
類似的,不管是什么瀏覽器,只要它跟 Safari 3 使用的是同一個版本的 WebKit,那么該瀏覽器也就跟 Safari 3 具備同樣的功能。
因此,我們需要編寫的腳本將主要檢測五大呈現引擎: IE、Gecko、WebKit、KHTML 和 Opera。
為了不在全局作用域中添加多余的變量,我們將使用模塊增強模式來封裝檢測腳本。
檢測到一個呈現引擎后,其 client.engine 中對應的屬性將被設置成一個大於 0 的值,該值可以轉換成布爾值的 true。這樣就可以在 if 語句中檢測相應的屬性,以確定當前使用的呈現引擎,連具體的版本號都不需要考慮。
鑒於每個屬性都包含一個浮點數值,因此有可能丟失某些版本信息。例如,將字符串"1.8.1"傳入 parseFloat() 后悔得到數值 1.8。不過,在必要的時候,可以檢測 ver 屬性,該屬性中保存着完整的版本信息。
正確的識別呈現引擎關鍵是檢測順序要正確。
首先應該檢測的是 Opera
if ( window.opera ){ engine.ver = window.opera.version(); engine.opera = parseFloat( engine.ver ); }
其次檢測的事 WebKit
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); }
接下來測試 KHTML
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); }
然后是 Gecko
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); } else if ( /rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){ engine.ver = RegExp["$1"]; engine.gecko = parseFloat(engine.ver); }
最后一個就是ie了
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); } else if ( /rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){ engine.ver = RegExp["$1"]; engine.gecko = parseFloat(engine.ver); } else if (/MSIE ([^;]+)/.test(ua)){ engine.ver = browser.ver = RegExp["$1"]; engine.ie = browser.ie = parseFloat(engine.ver); }
2)識別瀏覽器
大多數情況下,識別了瀏覽器的呈現引擎就足以為我們采取正確的操作提供依據了。可是,只有呈現引擎還不恩能夠說明存在所需的JavaScript功能。
var client = function(){ //呈現引擎 var engine = { ie : 0, gecko : 0, webkit : 0, khtml : 0, opera : 0, //具體的版本號 ver : null }; var browser = { //瀏覽器 ie : 0, firefox : 0, konq : 0, opera : 0, chrome : 0, safari : 0, //具體的版本 ver : null }; //再次檢測呈現引擎、平台和設備 return { engine : engine, browser : browser }; }();
由於大多數瀏覽器與其呈現引擎密切相關,所以下面實力中檢測瀏覽器的代碼與呈現引擎的代碼是混合在一起的
//檢測呈現引擎及瀏覽器 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); //確定是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"]; borwser.safari = parseFloat(browser.ver); } else { //近似的確定版本號 var safariVersion = 1; if (engine.webkit < 100 ){ safariVersion = 1; } else if (engine.webkit < 312){ safariVersoin = 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 = RegExp["$1"]; engine.khtml = 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); }
3、識別平台
很多時候,只要知道呈現引擎就可以編寫出適合的代碼了,但在有些情況下,平台可能是必須關注的問題。為了檢測這些平台,需要再添加一個對象:
1 var client = function(){ 2 //呈現引擎 3 4 var engine = { 5 ie : 0, 6 gecko : 0, 7 webkit : 0, 8 khtml : 0, 9 opera : 0, 10 11 //具體的版本號 12 ver : null 13 }; 14 15 var browser = { 16 //瀏覽器 17 ie : 0, 18 firefox : 0, 19 konq : 0, 20 opera : 0, 21 chrome : 0, 22 safari : 0, 23 24 //具體的版本 25 ver : null 26 }; 27 28 var system = { 29 win : false, 30 mac : false, 31 xll : false 32 }; 33 34 //再次檢測呈現引擎、平台和設備 35 36 return { 37 engine : engine, 38 browser : browser, 39 system : system 40 }; 41 }(); 42 43 44 var p = navigator.platform; 45 system.win = p.indexOf("Win") == 0; 46 systemp.mac = p.indexOf("Mac") == 0; 47 system.xll = (p.indexOf("Xll")) == 1 || (p.indexOf("Linux") == 0);
然后還有識別移動設備的
完整的代碼如下:
1 var client = function(){ 2 //呈現引擎 3 4 var engine = { 5 ie : 0, 6 gecko : 0, 7 webkit : 0, 8 khtml : 0, 9 opera : 0, 10 11 //具體的版本號 12 ver : null 13 }; 14 15 var browser = { 16 //瀏覽器 17 ie : 0, 18 firefox : 0, 19 konq : 0, 20 opera : 0, 21 chrome : 0, 22 safari : 0, 23 24 //具體的版本 25 ver : null 26 }; 27 28 //平台、設備和操作系統 29 var system = { 30 win : false, 31 mac : false, 32 xll : false, 33 34 //移動設備 35 iphone : false, 36 ipod : false, 37 nokiaN : false, 38 winMobile : false, 39 macMobile : false, 40 41 //游戲系統 42 wii : false, 43 ps : false 44 }; 45 46 //檢測呈現引擎及瀏覽器 47 var ua = navigator.userAgent; 48 49 if ( window.opera ){ 50 51 engine.ver = window.opera.version(); 52 engine.opera = parseFloat( engine.ver ); 53 54 } else if ( /AppleWebKit\/(\S+)/.test(ua)){ 55 56 engine.ver = RegExp["$1"]; 57 engine.webkit = parseFloat(engine.ver); 58 59 //確定是Chrome 還是 Safari 60 if ( /Chrome\/(\S+)/.test(ua)){ 61 browser.ver = RegExp["$1"]; 62 browser.chrome = parseFloat(browser.ver); 63 } else if ( /Version\/(S+)/test(ua)){ 64 browser.ver = RegExp["$1"]; 65 borwser.safari = parseFloat(browser.ver); 66 } else { 67 //近似的確定版本號 68 var safariVersion = 1; 69 70 if (engine.webkit < 100 ){ 71 safariVersion = 1; 72 } else if (engine.webkit < 312){ 73 safariVersoin = 1.2; 74 } else if (engine.webkit < 412){ 75 safariVersion = 1.3; 76 } else { 77 safariVersion = 2; 78 } 79 80 browser.safari = browser.ver = safariVersion; 81 } 82 83 } else if ( /KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){ 84 engine.ver = RegExp["$1"]; 85 engine.khtml = parseFloat(engine.ver); 86 } else if ( /rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){ 87 engine.ver = RegExp["$1"]; 88 engine.gecko = parseFloat(engine.ver); 89 90 //確定不是Firefox 91 if( /Firefox\/(\S+)/.test(ua)){ 92 browser.ver = RegExp["$1"]; 93 browser.firefox = parseFloat(browser.ver); 94 } 95 96 } else if (/MSIE ([^;]+)/.test(ua)){ 97 engine.ver = browser.ver = RegExp["$1"]; 98 engine.ie = browser.ie = parseFloat(engine.ver); 99 } 100 101 //檢測瀏覽器 102 browser.ie = engine.ie; 103 browser.opera = engine.opera; 104 105 //檢測平台 106 var p = navigator.platform; 107 system.win = p.indexOf("Win") == 0; 108 systemp.mac = p.indexOf("Mac") == 0; 109 system.xll = (p.indexOf("Xll")) == 1 || (p.indexOf("Linux") == 0); 110 111 //檢測 Windows 操作系統 112 if( system.win){ 113 if( /Win(?:dows)?([^do]{2})\s?(\d+\.\d+)?/.test(ua)){ 114 if(RegExp["$1"] == "NT"){ 115 switch(RegExp["$2"]){ 116 case "5.0" : 117 system.win = "2000"; 118 break; 119 case "5.1" : 120 system.win = "XP"; 121 break; 122 case "6.0" : 123 system.win = "Vista"; 124 break; 125 default : 126 system.win = "NT"; 127 break; 128 } 129 } else if (RegExp["$1"] == "9x"){ 130 system.win = "ME"; 131 } else { 132 system.win = RegExp["$1"]; 133 } 134 } 135 } 136 137 //移動設備 138 system.iphone = ua.indexOf("iphone") > -1; 139 system.ipod = ua.indexOf("iPod") > -1; 140 system.nokiaN = ua.indexOf("NokiaN") > -1; 141 system.winMobile = (system.win == "CE"); 142 system.macMobile = (system.iphone || system.ipod); 143 144 //游戲系統 145 system.wii = ua.indexOf("Wii") > -1; 146 system.ps = /playstation/i.test(ua); 147 148 //再次檢測呈現引擎、平台和設備 149 150 return { 151 engine : engine, 152 browser : browser, 153 system : system 154 }; 155 }();