十四
navigator对象
检测插件
检测浏览器中是否安装了特定的插件是一种最常见的检测例程。对于非IE浏览器,plugins数组来到达这个目的。该数组中的每一项都包含下列属性:
- name:插件的名字。
- description:插件的描述。
- filename:插件的文件名。
- length:插件所处理的MIME类型数量。
//检测插件(在IE中无效)
function hasPlugin(name) {
name = name.toLowerCase();
for (var i = 0; i < navigator.plugins.length; i++) {
if (navigator.plugins[i].name.toLowerCase().indexOf(name) > -1) {
return true;
}
}
return false;
}
//检测Flash
alert(hasPlugin("Flash"));
//检测 QuickTime
alert(hasPlugin("QuickTime"));
检测IE中的插件比较麻烦,因为IE不支持Netscape式的插件。在IE中检测插件的唯一方式就是使用专有的ActiveXObject类型,并尝试创建一个特定插件的实例。IE是以COM对象的方式实现插件的,而COM对象使用唯一标识符来标识。因此,要想检查特定的插件,就必须知道其COM标识符。例如,Flash的标识符是ShockwaveFlash.ShockwaveFlash:
//检测IE 中的插件
function hasIEPlugin(name) {
try {
new ActiveXObject(name);
return true;
} catch (ex) {
return false;
}
}
//检测 Flash
alert(hasIEPlugin("ShockwaveFlash.ShockwaveFlash"));
//检测 QuickTime
alert(hasIEPlugin("QuickTime.QuickTime"));
鉴于检测这两种插件的方法差别太大,因此典型的做法是针对每个插件分别创建检测函数,而不是使用前面介绍的通用检测方法:
//检测所有浏览器中的Flash
function hasFlash() {
var result = hasPlugin("Flash");
if (!result) {
result = hasIEPlugin("ShockwaveFlash.ShockwaveFlash");
}
return result;
}
//检测所有浏览器中的QuickTime
function hasQuickTime() {
var result = hasPlugin("QuickTime");
if (!result) {
result = hasIEPlugin("QuickTime.QuickTime");
}
return result;
}
//检测 Flash
alert(hasFlash());
//检测 QuickTime
alert(hasQuickTime());
plugins集合有一个名叫refresh()的方法,用于刷新plugins以反映最新安装的插件。这个方法接收一个参数:表示是否应该重新加载页面的一个布尔值。
history对象
//后退一页
history.go(-1);
//前进一页
history.go(1);
//前进两页
history.go(2);
也可以给go()方法传递一个字符串参数,此时浏览器会跳转到历史记录中包含该字符串的第一个位置——可能后退,也可能前进,具体要看拿个位置最近。如果历史记录不包含该字符串,那么这个方法什么也不做:
//跳转到最近的wrox.com 页面
history.go("wrox.com");
//跳转到最近的nczonline.net 页面
history.go("nczonline.net");
//后退一页
history.back();
//前进一页
history.forward();
history对象还有一个length属性,保存着历史记录的数量。这个数量包括所有历史记录,即所有向后和向前的记录。对于加载到窗口、标签页或框架中的第一个页面而言,history.length等于0.通过像下面这样测试该属性的值,可以确定用户是否一开始就打开了你的页面:
if (history.length == 0){
//这应该是用户打开窗口后的第一个页面
}
客户端检测
面对浏览器普遍存在的不一致性问题,开发人员要么采取迁就各方的“最小公分母”策略,要么就得利用各种客户端检测方法,来突破或者规避种种局限性。
迄今为止,客户端检测仍然是web开发领域中一个饱受争议的话题。检测web客户端的手段很多,而且各有利弊。但最重要的还是要知道,不到万不得已,就不要使用客户端检测。只要能找到更通用的方法,就应该优先采用更通用的方法。一言以蔽之,先设计最通用的方案,然后再使用特定于浏览器的技术增强该方案。
能力检测
if (object.propertyInQuestion){
//使用 object.propertyInQuestion
}
举例,IE5.0之前的版本不支持document.getElementById()这个DOM方法。尽管可以使用非标准的document.all属性实现相同的目的,但IE的早期版本中确实不存在document.getElementById()。于是:
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!");
}
}
更可靠的能力检测
IE对typeof xhr.open会返回“unknown”。这意味着,在浏览器环境下测试任何对象的某个特性是否存在,要使用下面这个函数:
var xhr = new ActiveXObject("Microsoft.XMLHttp");
function isHostMethod(object, property) {
var t = typeof object[property];
return t == 'function' || (!!(t == 'object' && object[property])) || t == 'unknown';
}
result2 = isHostMethod(xhr, "open"); //true
result1 = isHostMethod(xhr, "foo"); //false
目前使用isHostMethod()方法还是比较靠谱的,因为它考虑到了浏览器的怪异行为。不过也要注意,宿主对象没有义务保持目前的实现方式不变,也不一定会模仿已有宿主对象的行为。所以,这个函数——以及其他类型函数,都不能百分百地保证永远可靠。作为考法人员,必须对自己要使用某个功能的风险作出理性的估计。
能力检测,不是浏览器检测
检测某个或某几个特性并不能够确定浏览器。下面给出的这段代码(或与之差不多的代码)可以在许多网站中看到,这种“浏览器检测”代码就是错误地依赖能力检测的典型示例:
//错误!还不够具体
var isFirefox = !!(navigator.vendor && navigator.vendorSub);
//错误!假设过头了
var isIE = !!(document.all && document.uniqueID);
实际上,根据浏览器不同将能力组合起来是更可取的方式。如果你知道自己的应用程序需要使用某些特定的浏览器特性,那么最好是一次性检测所有相关特性,而不要分别检测:
//确定浏览器是否支持Netscape风格的插件
var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);
//确定浏览器是否具有DOM1 级规定的能力
var hasDOM1 = !!(document.getElementById && document.createElement &&
document.getElementsByTagName);
在实际开发中,应该将能力检测作为确定下一步解决方案的依据,而不是用它来判断用户使用的是什么浏览器。
怪癖检测
怪癖检测的目标是识别浏览器的特殊行为。但与能力检测确认浏览器支持什么能力不同,怪癖检测是想要知道浏览器存在什么缺陷。这通常需要运行一小段代码,以确定某一特性不能正常工作。例如,IE8及更早版本中存在一个bug,即如果某个实例属性与[[Enumerable]]标记为false的某个原型属性同名,那么该实例属性将不会出现在for-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以前版本会枚举被隐藏的属性:
var hasEnumShadowsQuirk = function() {
var o = {
toString: function() {}
};
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属性访问。在服务器端,通过检测用户代理字符串来确定用户使用的浏览器是一种常见而且广为接受的做法。而在客户端,用户代理检测一般被当做一种万不得已才用的做法,其优先级排在能力检测和怪癖检测之后。
提到与用户代理字符串有关的争议,就不得不提到电子欺骗。所谓电子欺骗,就是指浏览器通过在自己的用户代理字符串加入一些错误或误导性信息,来达到欺骗服务器的目的。
HTTP规范明确规定,浏览器应该发送简短的用户代理字符串,指明浏览器的名称和版本号。
使用方法
用户代理检测一般适用于下列情形:
- 不能直接准确地使用能力检测或怪癖检测。例如,某些浏览器实现了为将来功能预留的存根(stub)函数。在这种情况下,仅测试响应的函数是否存在还得不到足够的信息。
- 同一款浏览器在不同平台下具备不同的能力。这时候,可能就有必要确定浏览器位于哪个平台下。
- 为了跟踪分析等目的需要知道确切的浏览器。