9.1 能力检测
常用也为人们广泛接受的客户端检测形式是能力检测(又称特性检测)。能力检测的目标不是 识别特定的浏览器,而是识别浏览器的能力。采用这种方式不必顾及特定的浏览器如何如何,只要确定 浏览器支持特定的能力,就可以给出解决方案。能力检测的基本模式如下:
if (object.propertyInQuestion)
{
//使用 object.propertyInQuestion }
举例来说,IE5.0之前的版本不支持 document.getElementById()这个 DOM方法。尽管可以使 用非标准的 document.all 属性实现相同的目的,但 IE 的早期版本中确实不存在 document.get- ElementById()。
要理解能力检测,首先必须理解两个重要的概念。如前所述,第一个概念就是先检测达成目的的常 用的特性。对前面的例子来说,就是要先检测 document.getElementById(),后检测 document.all。 先检测常用的特性可以保证代码优化,因为在多数情况下都可以避免测试多个条件。 第二个重要的概念就是必须测试实际要用到的特性。一个特性存在,不一定意味着另一个特性也存在。
9.1.1 更可靠的能力检测
能力检测对于想知道某个特性是否会按照适当方式行事(而不仅仅是某个特性存在)非常有用。上 一节中的例子利用类型转换来确定某个对象成员是否存在,但这样你还是不知道该成员是不是你想要 的。
检测某个属性是否存在并不能确定对象是否支持排序。更好的方式是检测 sort 是不是一个函数。
在可能的情况下,要尽量使用 typeof 进行能力检测。特别是,宿主对象没有义务让 typeof 返回 合理的值。令人发指的事儿就发生在 IE 中。大多数浏览器在检测到 document.createElement() 存在时,都会返回 true。
关于 typeof的行为不标准,IE中还可以举出例子来。ActiveX对象(只有 IE支持)与其他对象的行 为差异很大。
目前使用 isHostMethod()方法还是比较可靠的,因为它考虑到了浏览器的怪异行为。不过也要注 意,宿主对象没有义务保持目前的实现方式不变,也不一定会模仿已有宿主对象的行为。所以,这个函 数——以及其他类似函数,都不能百分之百地保证永远可靠。作为开发人员,必须对自己要使用某个功 能的风险作出理性的估计。 要
9.1.2 能力检测,不是浏览器检测
检测某个或某几个特性并不能够确定浏览器。下面给出的这段代码(或与之差不多的代码)可以在 许多网站中看到,这种“浏览器检测”代码就是错误地依赖能力检测的典型示例。
//错误!还不够具体
var isFirefox = !!(navigator.vendor && navigator.vendorSub);
//错误!假设过头了
var isIE = !!(document.all && document.uniqueID);
这两行代码代表了对能力检测的典型误用。以前,确实可以通过检测 navigator.vendor 和 navigator.vendorSub 来确定 Firefox浏览器。但是,Safari也依葫芦画瓢地实现了相同的属性。于是, 这段代码就会导致人们作出错误的判断。为检测 IE,代码测试了 document.all 和 document. uniqueID。这就相当于假设 IE将来的版本中仍然会继续存在这两个属性,同时还假设其他浏览器都不 会实现这两个属性。后,这两个检测都使用了双逻辑非操作符来得到布尔值(比先存储后访问的效果 更好)。 实际上,根据浏览器不同将能力组合起来是更可取的方式。如果你知道自己的应用程序需要使用某 些特定的浏览器特性,那么好是一次性检测所有相关特性,而不要分别检测。
9.2 怪癖检测
与能力检测类似,怪癖检测(quirks detection)的目标是识别浏览器的特殊行为。但与能力检测确 认浏览器支持什么能力不同,怪癖检测是想要知道浏览器存在什么缺陷(“怪癖”也就是 bug)。这通常 需要运行一小段代码,以确定某一特性不能正常工作。例如,IE8 及更早版本中存在一个 bug,即如果 某个实例属性与[[Enumerable]]标记为 false 的某个原型属性同名,那么该实例属性将不会出现在 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 循环中作为属性返回。
一般来说,“怪癖”都是个别浏览器所独有的,而且通常被归为 bug。在相关浏览器的新版本中,这 些问题可能会也可能不会被修复。由于检测“怪癖”涉及运行代码,因此我们建议仅检测那些对你有直 接影响的“怪癖”,而且好在脚本一开始就执行此类检测,以便尽早解决问题。
9.3 用户代理检测
第三种,也是争议大的一种客户端检测技术叫做用户代理检测。用户代理检测通过检测用户代理 字符串来确定实际使用的浏览器。在每一次 HTTP请求过程中,用户代理字符串是作为响应首部发送的, 而且该字符串可以通过 JavaScript的 navigator.userAgent 属性访问。在服务器端,通过检测用户代 理字符串来确定用户使用的浏览器是一种常用而且广为接受的做法。而在客户端,用户代理检测一般被 当作一种万不得已才用的做法,其优先级排在能力检测和(或)怪癖检测之后。
提到与用户代理字符串有关的争议,就不得不提到电子欺骗(spoofing)。所谓电子欺骗,就是指浏 览器通过在自己的用户代理字符串加入一些错误或误导性信息,来达到欺骗服务器的目的。要弄清楚这 个问题的来龙去脉,必须从 Web 问世初期用户代理字符串的发展讲起。
9.3.1 用户代理字符串的历史
HTTP规范(包括 1.0和 1.1版)明确规定,浏览器应该发送简短的用户代理字符串,指明浏览器的 名称和版本号。RFC 2616(即 HTTP 1.1协议规范)是这样描述用户代理字符串的:
“产品标识符常用于通信应用程序标识自身,由软件名和版本组成。使用产品标识符的大 多数领域也允许列出作为应用程序主要部分的子产品,由空格分隔。按照惯例,产品要按照相 应的重要程度依次列出,以便标识应用程序。”
上述规范进一步规定,用户代理字符串应该以一组产品的形式给出,字符串格式为:标识符/产品 版本号。但是,现实中的用户代理字符串则绝没有如此简单。
- 早期的浏览器
1993年,美国 NCSA(National Center for Supercomputing Applications,国家超级计算机中心)发布 了世界上第一款 Web 浏览器 Mosaic。
- Netscape Navigator 3和 Internet Explorer 3
1996年,Netscape Navigator 3发布,随即超越 Mosaic成为当时流行的 Web 浏览器。而用户代理 字符串只作了一些小的改变,删除了语言标记,同时允许添加操作系统或系统使用的 CPU等可选信息。
- Netscape Communicator 4和 IE4~IE8
1997年 8月,Netscapte Communicator 4发布(这一版将浏览器名字中的 Navigator换成了 Commu- nicator)。
- Gecko
Gecko是 Firefox的呈现引擎。当初的 Gecko是作为通用 Mozilla浏览器的一部分开发的,而第一个 采用 Gecko引擎的浏览器是 Netscape 6。为 Netscape 6编写的一份规范中规定了未来版本中用户代理字 符串的构成。
- WebKit
2003年,Apple公司宣布要发布自己的 Web 浏览器,名字定为 Safari。Safari的呈现引擎叫 WebKit, 是 Linux平台中 Konqueror浏览器的呈现引擎 KHTML的一个分支。几年后,WebKit 独立出来成为了一 个开源项目,专注于呈现引擎的开发。
这款新浏览器和呈现引擎的开发人员也遇到了与 Internet Explorer 3.0类似的问题:如何确保这款浏 览器不被流行的站点拒之门外?答案就是向用户代理字符串中放入足够多的信息,以便站点能够信任它 与其他流行的浏览器是兼容的。
- Konqueror
与 KDE Linux集成的 Konqueror,是一款基于 KHTML开源呈现引擎的浏览器。尽管 Konqueror只 能在 Linux中使用,但它也有数量可观的用户。
- Chrome
谷歌公司的 Chrome浏览器以 WebKit 作为呈现引擎,但使用了不同的 JavaScript引擎。在 Chrome 0.2 这个初的 beta版中,用户代理字符串完全取自 WebKit,只添加了一段表示 Chrome版本号的信息
- Opera
仅就用户代理字符串而言,Opera 应该是有争议的一款浏览器了。Opera 默认的用户代理字符串 是所有现代浏览器中合理的——正确地标识了自身及其版本号。
- iOS和 Android
移动操作系统 iOS和 Android默认的浏览器都基于 WebKit,而且都像它们的桌面版一样,共享相同 的基本用户代理字符串格式
9.3.2 用户代理字符串检测技术
考虑到历史原因以及现代浏览器中用户代理字符串的使用方式,通过用户代理字符串来检测特定的 浏览器并不是一件轻松的事。因此,首先要确定的往往是你需要多么具体的浏览器信息。一般情况下, 知道呈现引擎和低限度的版本就足以决定正确的操作方法了。
- 识别呈现引擎
如前所述,确切知道浏览器的名字和版本号不如确切知道它使用的是什么呈现引擎。如果 Firefox、 Camino 和 Netscape 都使用相同版本的 Gecko,那它们一定支持相同的特性。类似地,不管是什么浏览 器,只要它跟 Safari 3使用的是同一个版本的 WebKit,那么该浏览器也就跟 Safari 3具备同样的功能。 因此,我们要编写的脚本将主要检测五大呈现引擎:IE、Gecko、WebKit、KHTML和 Opera。
要正确地识别呈现引擎,关键是检测顺序要正确。由于用户代理字符串存在诸多不一致的地方,如 果检测顺序不对,很可能会导致检测结果不正确。为此,第一步就是识别 Opera,因为它的用户代理字符串有可能完全模仿其他浏览器。我们不相信 Opera,是因为(任何情况下)其用户代理字符串(都) 不会将自己标识为 Opera。
要识别 Opera,必须得检测 window.opera 对象。Opera 5及更高版本中都有这个对象,用以保存 与浏览器相关的标识信息以及与浏览器直接交互。在 Opera 7.6及更高版本中,调用 version()方法可 以返回一个表示浏览器版本的字符串,而这也是确定Opera版本号的佳方式。要检测更早版本的Opera, 可以直接检查用户代理字符串,因为那些版本还不支持隐瞒身份。不过,2007底 Opera的高版本已经 是 9.5 了,所以不太可能有人还在使用 7.6 之前的版本。
应该放在第二位检测的呈现引擎是 WebKit。因为 WebKit 的用户代理字符串中包含"Gecko"和 "KHTML"这两个子字符串,所以如果首先检测它们,很可能会得出错误的结论。
不过,WebKit 的用户代理字符串中的"AppleWebKit"是独一无二的,因此检测这个字符串合适。
接下来要测试的呈现引擎是 KHTML。同样,KHTML 的用户代理字符串中也包含"Gecko",因此 在排除 KHTML之前,我们无法准确检测基于 Gecko的浏览器。KHTML的版本号与 WebKit 的版本号 在用户代理字符串中的格式差不多,因此可以使用类似的正则表达式。此外,由于 Konqueror 3.1 及更 早版本中不包含 KHTML的版本,故而就要使用 Konqueror的版本来代替。
在排除了 WebKit 和 KHTML之后,就可以准确地检测 Gecko了。但是,在用户代理字符串中,Gecko 的版本号不会出现在字符串"Gecko"的后面,而是会出现在字符串"rv:"的后面。
后一个要检测的呈现引擎就是 IE了。IE的版本号位于字符串"MSIE"的后面、一个分号的前面, 因此相应的正则表达式非常简单
- 识别浏览器
大多数情况下,识别了浏览器的呈现引擎就足以为我们采取正确的操作提供依据了。可是,只有呈 现引擎还不能说明存在所需的 JavaScript功能。苹果公司的 Safari浏览器和谷歌公司的 Chrome浏览器都 使用 WebKit 作为呈现引擎,但它们的 JavaScript引擎却不一样。在这两款浏览器中,client.webkit 都会返回非 0值,但仅知道这一点恐怕还不够。对于它们,有必要像下面这样为 client 对象再添加一些 新的属性。
为了检测 Chrome和 Safari,我们在检测引擎的代码中添加了 if语句。提取 Chrome的版本号时,需 要查找字符串"Chrome/"并取得该字符串后面的数值。而提取 Safari 的版本号时,则需要查找字符串 "Version/"并取得其后的数值。由于这种方式仅适用于 Safari 3 及更高版本,因此需要一些备用的代 码,将 WebKit 的版本号近似地映射为 Safari的版本号(参见上一小节中的表格)。
- 识别平台
很多时候,只要知道呈现引擎就足以编写出适当的代码了。但在某些条件下,平台可能是必须关注 的问题。那些具有各种平台版本的浏览器(如 Safari、Firefox 和 Opera)在不同的平台下可能会有不同 的问题。目前的三大主流平台是 Windows、Mac 和 Unix(包括各种 Linux)。
- 识别 Windows操作系统
在 Windows平台下,还可以从用户代理字符串中进一步取得具体的操作系统信息。在 Windows XP 之前,Windows有两种版本,分别针对家庭用户和商业用户。针对家庭用户的版本分别是 Windows 95、 98和 Windows ME。而针对商业用户的版本则一直叫做 Window NT,后由于市场原因改名为 Windows 2000。这两个产品线后来又合并成一个由Windows NT发展而来的公共的代码基,代表产品就是Windows XP。随后,微软在 Windows XP基础上又构建了 Windows Vista。
- 识别移动设备
2006年到 2007年,移动设备中Web 浏览器的应用呈爆炸性增长。四大主要浏览器都推出了手机 版和在其他设备中运行的版本。要检测相应的设备,第一步是为要检测的所有移动设备添加属性,然后,通常简单地检测字符串"iPhone"、"iPod"和"iPad",就可以分别设置相应属性的值了
9.3.4 使用方法
我们在前面已经强调过了,用户代理检测是客户端检测的后一个选择。只要可能,都应该优先采 用能力检测和怪癖检测。用户代理检测一般适用于下列情形。
-
不能直接准确地使用能力检测或怪癖检测。例如,某些浏览器实现了为将来功能预留的存根 (stub)函数。在这种情况下,仅测试相应的函数是否存在还得不到足够的信息。
-
同一款浏览器在不同平台下具备不同的能力。这时候,可能就有必要确定浏览器位于哪个平 台下。
-
为了跟踪分析等目的需要知道确切的浏览器。