JavaScript第九章客户端检测
-
能力检测
-
能力检测(又称特性检测)。能力检测的目标不是识别特定的浏览器,而是识别浏览器的能力。也就是因为,有些函数,方法在某些浏览器里面用不了,使用要检测一下,防止出现问题
-
两个重要概念:
- 先检测达成目的的最常用的特性。
- 是必须测试实际要用到的特性。一个特性存在,不一定意味着另一个特性也存
在。
-
更可靠的能力检测
- 检测某个特性是不是一个函数而不是检测它是不是一个对象,因为有时候就算知道某个对象存在,也无法判定是不是我们想要的。
-
能力检测,不是浏览器检测
- 检测某个或某几个特性并不能够确定浏览器,所以根据浏览器不同将能力组合起来是更可取。或者知道自己的应用程序需要使用某些特定的浏览器特性,那么最好是一次性检测所有相关特性,而不要分别检测。
-
-
怪癖检测
-
与能力检测类似,怪癖检测(quirks detection)的目标是识别浏览器的特殊行为。但与能力检测确认浏览器支持什么能力不同,怪癖检测是想要知道浏览器存在什么缺陷(“怪癖”也就是 bug)。
-
-
用户代理检测
-
用户代理检测通过检测用户代理
字符串来确定实际使用的浏览器。在每一次 HTTP 请求过程中,用户代理字符串是作为响应首部发送的,
而且该字符串可以通过 JavaScript 的 navigator.userAgent 属性访问。在服务器端,通过检测用户代
理字符串来确定用户使用的浏览器是一种常用而且广为接受的做法。而在客户端,用户代理检测一般被当作一种万不得已才用的做法,其优先级排在能力检测和(或)怪癖检测之后。因为浏
览器通过在自己的用户代理字符串加入一些错误或误导性信息,来达到欺骗服务器。 -
用户代理字符串的历史
- HTTP 1.1 协议规定用户代理字符串应该以一组产品的形式给出,字符串格式为:标识符/产品版本号。
- 1.早期的浏览器
Netscape Navigator 2:Mozilla/版本号[语言] (平台; 加密类型)
Netscape Navigator 3:Mozilla/版本号 (平台; 加密类型 [; 操作系统或 CPU 说明])
Internet Explorer 3:Mozilla/2.0(compatible; MSIE 版本号; 操作系统)
Netscapte Communicator 4:Mozilla/版本号 (平台; 加密类型 [; 操作系统或 CPU 说明])
Internet Explorer 4:Mozilla/4.0(compatible; MSIE 版本号; 操作系统)
IE8:Mozilla/4.0 (compatible; MSIE 版本号; 操作系统; Trident/Trident 版本号)
IE9:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Netscape 6:Mozilla/Mozilla 版本号 (平台; 加密类型; 操作系统或 CPU; 语言; 预先发行版本)
Gecko/Gecko 版本号 应用程序或产品/应用程序或产品版本号
WebKit:Mozilla/5.0 (平台; 加密类型; 操作系统或 CPU; 语言) AppleWebKit/AppleWebKit 版本号
(KHTML, like Gecko) Safari/Safari 版本号
Safari 3.0: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
Konqueror :Mozilla/5.0 (compatible; Konqueror/ 版本号; 操作系统或 CPU )
Chrome 0.2:Mozilla/5.0 ( 平台; 加密类型; 操作系统或 CPU; 语言)AppleWebKit/AppleWebKit 版本号 (KHTML,like Gecko) Chrome/ Chrome 版本号 Safari/ Safari 版本
Chrome 7 :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
Opera 8:Opera/ 版本号 (操作系统或 CPU; 加密类型; 语言)
Opera 10:Opera/9.80 (Windows NT 6.1; U; en) Presto/2.6.30 Version/10.63
移动操作系统 iOS 和 Android 默认的浏览器都基于 WebKit,而且都像它们的桌面版一样,共享相同的基本用户代理字符串格式。
iOS:Mozilla/5.0 (平台; 加密类型; 操作系统或 CPU like Mac OS X; 语言)AppleWebKit/AppleWebKit 版本号 (KHTML, like Gecko) Version/浏览器版本号Mobile/移动版本号 Safari/Safari 版本号
Android:Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91)AppleWebKit/533.1 (KHTML, like Gecko)Version/4.0 MobileSafari/533.1 -
用户代理字符串检测技术
-
if (isIE6 || isIE7) { //不推荐!!! //代码 }
像这样限制最低版本就ok了,就不用后期有新本出来又要去改。 - 识别呈现引擎
检测浏览器的引擎往往比检测浏览器是什么更加高效目前脚本将主要检测五大呈现引擎:IE、Gecko、WebKit、KHTML 和 Opera。检测脚本的基本代码结构如下
var engine = { //呈现引擎 ie: 0, gecko: 0, webkit: 0, khtml: 0, opera: 0, //具体的版本号 ver: null }; //在此检测呈现引擎、平台和设备 return { engine : engine }; }();
这里声明了一个名为 client 的全局变量,用于保存相关信息。匿名函数内部定义了一个局部变量
engine,它是一个包含默认设置的对象字面量。在这个对象字面量中,每个呈现引擎都对应着 一个属性,属性的值默认为 0。如果检测到了哪个呈现引擎,那么就以浮点数值形式将该引擎的版本 号写入相应的属性。而呈现引擎的完整版本(是一个字符串),则被写入 ver 属性。if (client.engine.ie) { //如果是 IE,client.ie 的值应该大于 0 //针对 IE 的代码 } else if (client.engine.gecko > 1.5){ if (client.engine.ver == "1.8.1"){ //针对这个版本执行某些操作 } } 然后就可以根据不同的浏览器,用不同的操作。
要正确地识别呈现引擎,关键是检测顺序要正确。由于用户代理字符串存在诸多不一致的地方,如果 检测顺序不对,很可能会导致检测结果不正确。 首先就是识别 Opera,识别 Opera,必须得检测 window.opera 对象
第一步
if (window.opera){ engine.ver = window.opera.version(); engine.opera = parseFloat(engine.ver); }
第二位检测的呈现引擎是 WebKit,因为WebKit 的用户代理字符串中的"AppleWebKit"是独一无二的,因此检测这个字符串最合适。
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 的用户代理字符串中也包含"Gecko",因此在排除 KHTML 之前,我们无法准确检测基于 Gecko 的浏览器。KHTML 的版本号与 WebKit 的版本号在用户代理字符串中的格式差不多,因此可以使用类似的正则表达式。此外,由于 Konqueror 3.1 及更早版本中不包含 KHTML 的版本,故而就要使用 Konqueror 的版本来代替。
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); }
在排除了 WebKit 和 KHTML 之后,就可以准确地检测 Gecko 了用户代理字符串中,Gecko的版本号不会出现在字符串"Gecko"的后面,而是会出现在字符串"rv:"的后面。
-
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);
}
最后一个要检测的呈现引擎就是 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)) {
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);
}
- 识别浏览器
大多数情况下,识别了浏览器的呈现引擎就足以为我们采取正确的操作提供依据了。可是,只有呈
现引擎还不能说明存在所需的 JavaScript 功能。苹果公司的 Safari 浏览器和谷歌公司的 Chrome 浏览器都使用 WebKit 作为呈现引擎,但它们的 JavaScript 引擎却不一样。在这两款浏览器中,client.webkit都会返回非 0值,但仅知道这一点恐怕还不够。对于它们,有必要像下面这样为 client 对象再添加一些新的属性。
var client = function(){
var engine = {
//呈现引擎
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//具体的版本
ver: null
};
var browser = {
//浏览器
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
//具体的版本
ver: null
};
//在此检测呈现引擎、平台和设备
return {
engine: engine,
browser: browser// 用于保存每个主要浏览器的属性
};
}();
由于大多数浏览器与其呈现引擎密切相关,所以下面示例中检测浏览器的代码与检测呈现引擎的代码是混合在一起的。
//检测呈现引擎及浏览器
var ua = navigator.userAgent;
if (window.opera){
engine.ver = browser.ver = window.opera.version();
engine.opera = browser.opera = parseFloat(engine.ver);
} else if (/AppleWebKit\/(\S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
//确定是 Chrome 还是 Safari
if (/Chrome\/(\S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.chrome = parseFloat(browser.ver);
} else if (/Version\/(\S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.safari = parseFloat(browser.ver);
} else {
//近似地确定版本号
var safariVersion = 1;
if (engine.webkit < 100){
safariVersion = 1;
} else if (engine.webkit < 312){
safariVersion = 1.2;
} else if (engine.webkit < 412){
safariVersion = 1.3;
} else {
safariVersion = 2;
}
browser.safari = browser.ver = safariVersion;
}
} 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);
}
- 识别平台
- 那些具有各种平台版本的浏览器(如Safari、Firefox 和 Opera)在不同的平台下可能会有不同的问题。
- `var client = function(){
var engine = {
//呈现引擎
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//具体的版本号
ver: null
};
var browser = {
//浏览器
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
//具体的版本号
ver: null
};
var system = {
win: false,
mac: false,
x11: false
};
//在此检测呈现引擎、平台和设备
return {
engine: engine,
browser: browser,
system: system
};
}(); `
system:其中,win 属性表示是否为
Windows 平台,mac 表示 Mac,而 x11 表示 Unix。
- 识别 Windows 操作系统
第一步就是匹配 Windows 95 和 Windows 98 这两个字符串。/Win(?:dows )?([^do]{2})/
Gecko 在表示 Windows NT 时会在末尾添加"4.0",与其查找实际的字符串,不如像下面这样查找小数值更合适。/Win(?:dows )?([^do]{2}){(\d+\.\d+)?/
正则表达式中就包含了第二个捕获组,用于取得 NT 的版本号。由于该版本号对于 Windows
95 和 Windows 98 而言是不存在的,所以必须设置为可选。这个模式与 Opera 表示 Windows NT 的字符串之间唯一的区别,就是"NT"与"4.0"之间的空格。/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/
具体来说,第一个捕获组将会匹配 95、98、9x、NT、ME 或 XP。第二个捕获组则只针对Windows ME 及所有 Windows NT 的变体。这个信息可以作为具体的操作系统信息保存在 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,那么就使用这个正则表达式从用户代理字符串中提取具体的信息。鉴于 Windows 将来的某个版本也许不能使用这个方法来检测,所以第一步应该先检测用户代理字符串是否与这个模式匹配。在模式匹配情况下,第一个捕获组中可能会包含"95"、“98”、“9x"或"NT”。如果这个值是"NT",可以将 system.win 设置为相应操作系统的字符串;如果是"9x",那么 system.win
就要设置成"ME";如果是其他值,则将所捕获的值直接赋给 system.win。
- 识别移动设备
四大主要浏览器都推出了手机 版和在其他设备中运行的版本。要检测相应的设备,第一步是为要检测的所有移动设备添加属性,
var client = function(){
var engine = {
//呈现引擎
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//具体的版本号
ver: null
};
var browser = {
//浏览器
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
//具体的版本号
ver: null
};
var system = {
win: false,
mac: false,
x11: false,
//移动设备
iphone: false,
ipod: false,
ipad: false,
ios: false,
android: false,
nokiaN: false,
winMobile: false };
//在此检测呈现引擎、平台和设备
return {
engine: engine,
browser: browser,
system: system
};
}();
然后,通常简单地检测字符串"iPhone"、"iPod"和"iPad",就可以分别设置相应属性的值了
//检测 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; //不能真正检测出来,所以只能猜测
}
}
检查系统是不是 Mac OS、字符串中是否存在"Mobile",可以保证无论是什么版本,system.ios
中都不会是 0。然后,再使用正则表达式确定是否存在 iOS 的版本号。如果有,将 system.ios 设置为
表示版本号的浮点值;否则,将版本设置为 2。(因为没有办法确定到底是什么版本,所以设置为更早的
版本比较稳妥。)
检测 Android 操作系统也很简单,也就是搜索字符串"Android"并取得紧随其后的版本号。
//检测 Android 版本
if (/Android (\d+\.\d+)/.test(ua)){
system.android = parseFloat(RegExp.$1);
}
诺基亚 N 系列手机在用户代理字符串中声称使用的是"Safari",但实际上并不是 Safari,尽
管确实是基于 WebKit 引擎。只要像下面检测一下用户代理字符串中是否存在"NokiaN",就足以确定是不是该系列的手机了。
system.nokiaN = ua.indexOf("NokiaN") > -1;
if (system.win == "CE"){ system.winMobile = system.win } else if (system.win == "Ph"){ if(/Windows Phone OS (\d+.\d+)/.test(ua)){; system.win = "Phone"; system.winMobile =parseFloat(RegExp["$1"]); } }
如果 system.win 的值是"CE",就说明是老版 本的 Windows Mobile,因此 system.winMobile
会被设置为相同的值(只能知道这个信息)。如果 system.win 的值是"Ph",那么这个设备就可能是Windows Phone 7 或更新版本。因此就用正则表达式来测试格式并提取版本号,将 system.win 的值重置为"Phone",而将 system.winMobile 设置为版本号。
- 识别游戏系统
-
任天堂 Wii 和 Playstation 3 或
者内置 Web 浏览器,或者提供了浏览器下载。Wii 中的浏览器实际上是定制版的 Opera,是专门为 WiiRemote 设计的。Playstation 的浏览器是自己开发的,没有基于前面提到的任何呈现引擎。
这两个浏览器中的用户代理字符串如下所示:
Opera/9.10 (Nintendo Wii;U; ; 1621; en)
Mozilla/5.0 (PLAYSTATION 3; 2.00) -
在检测这些设备以前,我们必须先为 client.system 中添加适当的属性
-
var client = function(){ var engine = { //呈现引擎 ie: 0, gecko: 0, webkit: 0, khtml: 0, opera: 0, //具体的版本号 ver: null }; var browser = { //浏览器 ie: 0, firefox: 0, safari: 0, konq: 0, opera: 0, chrome: 0, //具体的版本号 ver: null }; var system = { win: false, mac: false, x11: false, //移动设备 iphone: false, ipod: false, ipad: false, ios: false, android: false, nokiaN: false, winMobile: false, //游戏系统 wii: false, ps: false }; //在此检测呈现引擎、平台和设备 return { engine: engine, browser: browser, system: system }; }();
然后
system.wii = ua.indexOf(“Wii”) > -1;
system.ps = /playstation/i.test(ua);
对于 Wii,只要检测字符串"Wii"就够了,而其他代码将发现这是一个 Opera 浏览器,并将正确的
版本号保存在 client.browser.opera 中。
-
-
总结
- 能力检测:在编写代码之前先检测特定浏览器的能力。
- 怪癖检测:怪癖实际上是浏览器实现中存在的 bug
- 用户代理检测:通过检测用户代理字符串来识别浏览器。(用户代理字符串中包含大量与浏览器
有关的信息,包括浏览器、平台、操作系统及浏览器版本) - 不能直接准确地使用能力检测或怪癖检测
- 同一款浏览器在不同平台下具备不同的能力
- 在决定使用哪种客户端检测方法时,一般应优先考虑使用能力检测。怪癖检测是确定应该如何处理
代码的第二选择。而用户代理检测则是客户端检测的最后一种方案,因为这种方法对用户代理字符串具
有很强的依赖性。