9.3.2 用户代理字符串检测技术

优质
小牛编辑
121浏览
2023-12-01

考虑到历史原因以及现代浏览器中用户代理字符串的使用方式,通过用户代理字符串来检测特定的浏览器并不是一件轻松的事。因此,首先要确定的往往是你需要多么具体的浏览器信息。一般情况下,知道呈现引擎和最低限度的版本就足以决定正确的操作方法了。例如,我们不推荐使用下列代码:

if (isIE6 || isIE7) { //不推荐!!!
    //代码
}

这个例子是想要在浏览器为IE6 或IE7 时执行相应代码。这种代码其实是很脆弱的,因为它要依据特定的版本来决定做什么。如果是IE8 怎么办呢?只要IE 有新版本出来,就必须更新这些代码。不过,像下面这样使用相对版本号则可以避免此问题:

if (ieVer >=6){
    //代码
}

这个例子首先检测IE 的版本号是否至少等于6,如果是则执行相应操作。这样就可以确保相应的代码将来照样能够起作用。我们下面的浏览器检测脚本就将本着这种思路来编写。

1. 识别呈现引擎

如前所述,确切知道浏览器的名字和版本号不如确切知道它使用的是什么呈现引擎。如果Firefox、Camino 和Netscape 都使用相同版本的Gecko,那它们一定支持相同的特性。类似地,不管是什么浏览器,只要它跟Safari 3 使用的是同一个版本的WebKit,那么该浏览器也就跟Safari 3 具备同样的功能。

因此,我们要编写的脚本将主要检测五大呈现引擎:IE、Gecko、WebKit、KHTML 和Opera。为了不在全局作用域中添加多余的变量,我们将使用模块增强模式来封装检测脚本。检测脚本的基本代码结构如下所示:

var client = function() {
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") {//针对这个版本执行某些操作
}
}

在检测到一个呈现引擎之后,其client.engine 中对应的属性将被设置为一个大于0 的值,该值可以转换成布尔值true。这样,就可以在if 语句中检测相应的属性,以确定当前使用的呈现引擎,连具体的版本号都不必考虑。鉴于每个属性都包含一个浮点数值,因此有可能丢失某些版本信息。例如,将字符串"1.8.1"传入parseFloat()后会得到数值1.8。不过,在必要的时候可以检测ver 属性,该属性中会保存完整的版本信息。

要正确地识别呈现引擎,关键是检测顺序要正确。由于用户代理字符串存在诸多不一致的地方,如果检测顺序不对,很可能会导致检测结果不正确。为此,第一步就是识别Opera,因为它的用户代理字符串有可能完全模仿其他浏览器。我们不相信Opera,是因为(任何情况下)其用户代理字符串(都)不会将自己标识为Opera。

要识别Opera,必须得检测window.opera 对象。Opera 5 及更高版本中都有这个对象,用以保存与浏览器相关的标识信息以及与浏览器直接交互。在Opera 7.6 及更高版本中,调用version()方法可以返回一个表示浏览器版本的字符串,而这也是确定Opera版本号的最佳方式。要检测更早版本的Opera,可以直接检查用户代理字符串,因为那些版本还不支持隐瞒身份。不过,2007 底Opera 的最高版本已经是9.5 了,所以不太可能有人还在使用7.6 之前的版本。那么,检测呈现引擎代码的第一步,就是编写如下代码:

if (window.opera){
    engine.ver = window.opera.version();
    engine.opera = parseFloat(engine.ver);
}

这里,将版本的字符串表示保存在了engine.ver 中,将浮点数值表示的版本保存在了engine.opera 中。如果浏览器是Opera,测试window.opera 就会返回true;否则,就要看看是其他的什么浏览器了。

应该放在第二位检测的呈现引擎是WebKit。因为WebKit 的用户代理字符串中包含"Gecko"和"KHTML"这两个子字符串,所以如果首先检测它们,很可能会得出错误的结论。

不过,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);
}

代码首先将用户代理字符串保存在变量ua 中。然后通过正则表达式来测试其中是否包含字符串"AppleWebKit",并使用捕获组来取得版本号。由于实际的版本号中可能会包含数字、小数点和字母,所以捕获组中使用了表示非空格的特殊字符(\S)。用户代理字符串中的版本号与下一部分的分隔符是一个空格,因此这个模式可以保证捕获所有版本信息。test()方法基于用户代理字符串运行正则表达式。如果返回true,就将捕获的版本号保存在engine.ver 中,而将版本号的浮点表示保存在engine.webkit 中。WebKit 版本与Safari 版本的详细对应情况如下表所示。

有时候,Safari 版本并不会与WebKit 版本严格地一一对应,也可能会存在某些小版本上的差异。这个表中只是列出了最可能的WebKit 版本,但不保证精确。

接下来要测试的呈现引擎是KHTML。同样,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);
}

与前面一样,由于KHTML 的版本号与后继的标记之间有一个空格,因此仍然要使用特殊的非空格字符来取得与版本有关的所有字符。然后,将字符串形式的版本信息保存在engine.ver 中,将浮点数值形式的版本保存在engin.khtml 中。如果KHTML 不在用户代理字符串中,那么就要匹配Konqueror后跟一个斜杠,再后跟不包含分号的所有字符。

在排除了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);
}

Gecko 的版本号位于字符串"rv:"与一个闭括号之间,因此为了提取出这个版本号,正则表达式要查找所有不是闭括号的字符,还要查找字符串"Gecko/"后跟8 个数字。如果上述模式匹配,就提取出版本号并将其保存在相应的属性中。Gecko 版本号与Firefox 版本号的对应关系如下表所示。

与Safari 跟WebKit 一样,Firefox 与Gecko 的版本号也不一定严格对应。

最后一个要检测的呈现引擎就是IE 了。IE 的版本号位于字符串"MSIE"的后面、一个分号的前面,因此相应的正则表达式非常简单,如下所示:

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);
}

以上呈现引擎检测脚本的最后一部分,就是在正则表达式中使用取反的字符类来取得不是分号的所有字符。IE 通常会保证以标准浮点数值形式给出其版本号,但有时候也不一定。因此,取反的字符类[^;]可以确保取得多个小数点以及任何可能的字符。

2. 识别浏览器

大多数情况下,识别了浏览器的呈现引擎就足以为我们采取正确的操作提供依据了。可是,只有呈现引擎还不能说明存在所需的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
};
} ();

代码中又添加了私有变量browser,用于保存每个主要浏览器的属性。与engine 变量一样,除了当前使用的浏览器,其他属性的值将保持为0;如果是当前使用的浏览器,则这个属性中保存的是浮点数值形式的版本号。同样,ver 属性中在必要时将会包含字符串形式的浏览器完整版本号。由于大多数浏览器与其呈现引擎密切相关,所以下面示例中检测浏览器的代码与检测呈现引擎的代码是混合在一起的。

//检测呈现引擎及浏览器
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);
}

对Opera 和IE 而言,browser 对象中的值等于engine 对象中的值。对Konqueror 而言,browser.konq 和browser.ver 属性分别等于engine.khtml 和engine.ver 属性。

为了检测Chrome 和Safari,我们在检测引擎的代码中添加了if 语句。提取Chrome 的版本号时,需要查找字符串"Chrome/"并取得该字符串后面的数值。而提取Safari 的版本号时,则需要查找字符串"Version/"并取得其后的数值。由于这种方式仅适用于Safari 3 及更高版本,因此需要一些备用的代码,将WebKit 的版本号近似地映射为Safari 的版本号(参见上一小节中的表格)。

在检测Firefox 的版本时,首先要找到字符串"Firefox/",然后提取出该字符串后面的数值(即版本号)。当然,只有呈现引擎被判别为Gecko 时才会这样做。

有了上面这些代码之后,我们就可以编写下面的逻辑。
if (client.engine.webkit) { //if it’s WebKit
if (client.browser.chrome) {//执行针对Chrome 的代码
} else if (client.browser.safari) {//执行针对Safari 的代码
}
} else if (client.engine.gecko) {
if (client.browser.firefox) {//执行针对Firefox 的代码
} else {//执行针对其他Gecko 浏览器的代码
}
}

3. 识别平台

很多时候,只要知道呈现引擎就足以编写出适当的代码了。但在某些条件下,平台可能是必须关注的问题。那些具有各种平台版本的浏览器(如Safari、Firefox 和Opera)在不同的平台下可能会有不同的问题。目前的三大主流平台是Windows、Mac 和Unix(包括各种Linux)。为了检测这些平台,还需要像下面这样再添加一个新对象。

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
};
} ();

显然,上面的代码中又添加了一个包含3 个属性的新变量system。其中,win 属性表示是否为Windows 平台,mac 表示Mac,而x11 表示Unix。与呈现引擎不同,在不能访问操作系统或版本的情况下,平台信息通常是很有限的。对这三个平台而言,浏览器一般只报告Windows 版本。为此,新变量system 的每个属性最初都保存着布尔值false,而不是像呈现引擎属性那样保存着数字值。

在确定平台时,检测navigator.platform 要比检测用户代理字符串更简单,后者在不同浏览器中会给出不同的平台信息。而navigator.platform 属性可能的值包括"Win32"、"Win64"、"MacPPC"、"MacIntel"、"X11"和"Linux i686",这些值在不同的浏览器中都是一致的。检测平台的代码非常直观,如下所示:

var p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = (p.indexOf("X11") == 0) || (p.indexOf("Linux") == 0);

以上代码使用indexOf()方法来查找平台字符串的开始位置。虽然"Win32"是当前浏览器唯一支持的Windows 字符串,但随着向64 位Windows 架构的迁移,将来很可能会出现"Win64"平台信息值。为了对此有所准备,检测平台的代码中查找的只是字符串"Win"的开始位置。而检测Mac 平台的方式也类似,同样是考虑到了MacPPC 和MacIntel。在检测Unix 时,则同时检查了字符串"X11"和"Linux"在平台字符串中的开始位置,从而确保了代码能够向前兼容其他变体。

Gecko 的早期版本在所有Windows 平台中都返回字符串"Windows",在所有Mac 平台中则都返回字符串"Macintosh"。不过,这都是Firefox 1 发布以前的事了,Firefox 1确定了navigator.platform 的值。

4. 识别Windows 操作系统

在Windows 平台下,还可以从用户代理字符串中进一步取得具体的操作系统信息。在Windows XP之前,Windows 有两种版本,分别针对家庭用户和商业用户。针对家庭用户的版本分别是Windows 95、98 和Windows ME。而针对商业用户的版本则一直叫做Window NT,最后由于市场原因改名为Windows2000。这两个产品线后来又合并成一个由Windows NT 发展而来的公共的代码基,代表产品就是WindowsXP。随后,微软在Windows XP 基础上又构建了Windows Vista。

只有了解这些信息,才能搞清楚用户代理字符串中Windows 操作系统的具体版本。下表列出了不同浏览器在表示不同的Windows 操作系统时给出的不同字符串。

由于用户代理字符串中的Windows 操作系统版本表示方法各异,因此检测代码并不十分直观。好在,从Windows 2000 开始,表示操作系统的字符串大部分都还相同,只有版本号有变化。为了检测不同的Windows 操作系统,必须要使用正则表达式。由于使用Opera 7 之前版本的用户已经不多了,因此我们可以忽略这部分浏览器。

第一步就是匹配Windows 95 和Windows 98 这两个字符串。对这两个字符串,只有Gecko 与其他浏览器不同,即没有"dows",而且"Win"与版本号之间没有空格。要匹配这个模式,可以使用下面这个简单的正则表达式。

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

这个正则表达式中的捕获组会返回操作系统的版本。由于版本可能是任何两个字符编码(例如95、98、9x、NT、ME 及XP),因此要使用两个非空格字符。

Gecko 在表示Windows NT 时会在末尾添加"4.0",与其查找实际的字符串,不如像下面这样查找小数值更合适。

/Win(?:dows )?([^do]{2})(\d+\.\d+)?/
这样,正则表达式中就包含了第二个捕获组,用于取得NT 的版本号。由于该版本号对于Windows95 和Windows 98 而言是不存在的,所以必须设置为可选。这个模式与Opera 表示Windows NT 的字符串之间唯一的区别,就是"NT"与"4.0"之间的空格,这在模式中很容易添加。
/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/
经过一番修改之后,这个正则表达式也可以成功地匹配Windows ME、Windows XP 和Windows Vista的字符串了。具体来说,第一个捕获组将会匹配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。有了这些检测平台的代码后,我们就可以编写如下代码。

if (client.system.win) {
if (client.system.win == "XP") {//说明是XP
} else if (client.system.win == "Vista") {//说明是Vista
}
}

由于非空字符串会转换为布尔值true,因此可以将client.system.win 作为布尔值用在if 语句中。而在需要更多有关操作系统的信息时,则可以使用其中保存的字符串值。

5. 识别移动设备

2006 年到2007 年,移动设备中Web 浏览器的应用呈爆炸性增长。四大主要浏览器都推出了手机版和在其他设备中运行的版本。要检测相应的设备,第一步是为要检测的所有移动设备添加属性,如下所示。

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",就可以分别设置相应属性的值了。

system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipod = ua.indexOf("iPad") > -1;

除了知道iOS 设备,最好还能知道iOS 的版本号。在iOS 3 之前,用户代理字符串中只包含"CPU likeMac OS",后来iPhone 中又改成"CPU iPhone OS 3_0 like Mac OS X",iPad 中又改成"CPU OS 3_2like Mac OS X"。也就是说,检测iOS 需要正则表达式反映这些变化。

//检测iOS 版本
if (system.mac && ua.indexOf("Mobile") > -1) {
if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)) {system.ios = parseFloat(RegExp.$1.replace("_", "."));
} else {system.ios = 2; //不能真正检测出来,所以只能猜测
}
}

检查系统是不是Mac OS、字符串中是否存在"Mobile",可以保证无论是什么版本,system.ios中都不会是0。然后,再使用正则表达式确定是否存在iOS 的版本号。如果有,将system.ios 设置为表示版本号的浮点值;否则,将版本设置为2。(因为没有办法确定到底是什么版本,所以设置为更早的版本比较稳妥。)

检测Android 操作系统也很简单,也就是搜索字符串"Android"并取得紧随其后的版本号。

//检测Android 版本
if (/Android (\d+\.\d+)/.test(ua)) {
system.android = parseFloat(RegExp.$1);
}

由于所有版本的Android 都有版本值,因此这个正则表达式可以精确地检测所有版本,并将system.android 设置为正确的值。

诺基亚N 系列手机使用的也是WebKit,其用户代理字符串与其他基于WebKit 的手机很相似,例如:
Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 NokiaN95/11.0.026; Profile MIDP-2.0
Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413

虽然诺基亚N 系列手机在用户代理字符串中声称使用的是"Safari",但实际上并不是Safari,尽管确实是基于WebKit 引擎。只要像下面检测一下用户代理字符串中是否存在"NokiaN",就足以确定是不是该系列的手机了。

system.nokiaN = ua.indexOf("NokiaN") > -1;
在了解这些设备信息的基础上,就可以通过下列代码来确定用户使用的是什么设备中的WebKit 来访问网页:
if (client.engine.webkit) {
if (client.system.iOS) {//iOS 手机的内容
} else if (client.system.android) {//Android 手机的内容
} else if (client.system.nokiaN) {//诺基亚手机的内容
}
}

最后一种主要的移动设备平台是Windows Mobile(也称为Windows CE),用于Pocket PC 和Smartphone 中。由于从技术上说这些平台都属于Windows 平台,因此Windows 平台和操作系统都会返回正确的值。对于Windows Mobile 5.0 及以前版本,这两种设备的用户代理字符串非常相似,如下所示:

Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320)
Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Smartphone; 176x220)
第一个来自Pocket PC 中的移动Internet Explorer 4.01,第二个来自Smartphone 中的同一个浏览器。 当Windows 操作系统检测脚本检测这两个字符串时,system.win 将被设置为"CE",因此在检测Windows Mobile 时可以使用这个值:
system.winMobile = (system.win == "CE");
不建议测试字符串中的"PPC"或"Smartphone",因为在Windows Mobile 5.0 以后版本的浏览器中,这些记号已经被移除了。不过,一般情况下,只知道某个设备使用的是Windows Mobile 也就足够了。 Windows Phone 7 的用户代理字符串稍有改进,基本格式如下:
Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0) Asus;Galaxy6
其中,Windows 操作符的标识符与已往完全不同,因此在这个用户代理中client.system.win等于"Ph"。从中可以取得有关系统的更多信息:
//windows mobile
if (system.win == "CE") {
system.winMobile = system.win;
} else if (system.win == "Ph") {
if (/Windows Phone OS (\d+.\d+)/.test(ua)) {;system.win = "Phone";system.winMobile = parseFloat(RegExp["$1"]);
}
}

如果system.win 的值是"CE",就说明是老版本的Windows Mobile,因此system.winMobile会被设置为相同的值(只能知道这个信息)。如果system.win 的值是"Ph",那么这个设备就可能是Windows Phone 7 或更新版本。因此就用正则表达式来测试格式并提取版本号,将system.win 的值重置为"Phone",而将system.winMobile 设置为版本号。

6. 识别游戏系统

除了移动设备之外,视频游戏系统中的Web 浏览器也开始日益普及。任天堂Wii 和Playstation 3 或者内置Web 浏览器,或者提供了浏览器下载。Wii 中的浏览器实际上是定制版的Opera,是专门为WiiRemote 设计的。Playstation 的浏览器是自己开发的,没有基于前面提到的任何呈现引擎。这两个浏览器中的用户代理字符串如下所示:

Opera/9.10 (Nintendo Wii;U; ; 1621; en)
Mozilla/5.0 (PLAYSTATION 3; 2.00)

第一个字符串来自运行在Wii 中的Opera,它忠实地继承了Opera 最初的用户代理字符串格式(Wii上的Opera 不具备隐瞒身份的能力)。第二个字符串来自Playstation3,虽然它为了兼容性而将自己标识为Mozilla 5.0,但并没有给出太多信息。而且,设备名称居然全部使用了大写字母,让人觉得很奇怪;

强烈希望将来的版本能够改变这种情况。在检测这些设备以前,我们必须先为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 中。对于Playstation,我们则使用正则表达式来以不区分大小写的方式测试用户代理字符串。