一个陷阱及其规避方法
在使用属性检测之前,您需要知道正在被检测的属性的数据类型和缺省值。如果浏览器支持的属性值为0或者一个空的字符串,则包含该属性的引用的if条件表达式的求值结果就将等于 false(假),这会导致该属性不存在的假象,而实际上该属性是存在的。
这就是 JavaScript 核心语言中的操作符:typeof 发挥作用的地方。当您在任何表达式前增加这个操作符时,其结果就是一个字符串,代表表达式值的数据类型。举例来说,如果您正在测试的属性总是返回一个字符串(无论这个字符串是空字符串,还是带有文本),其条件表达式大致如下:
if (typeof someObject.someProperty == "string") {
// property exists, so use it
}
还有一个省事的方法可供选择,即可以通过确认该类型是否为除 undefined 之外的值,来测试属性是否存在,如下面的代码所示:
if (typeof someObject.someProperty != "undefined") {
// property exists, so use it
}
然而您还不能完全依赖这个操作符--在 Netscape Navigator 2 的核心语言中没有定义这个操作符。因此,您只能把这个技术用在特定的执行分支上,即确认了当前浏览器不是NN2的分支上,或者假定您的网站用户没有使用这个明显古老的浏览器。
评价您的需求
很难明确地描述一个如何进行对象检测的模板。每个页面的兼容性目标和对象检测的需求在某种程度上是不一样的。
在实现对象检测的过程中,可能最重要的一个步骤是明确您的需求。举例来说,如果您的脚本仅和元素的动态风格属性交互,则主要的对象检测任务就应该是选择合适的元素引用方法:是用 document.all 方法来照顾那些仍然使用 IE4 的落伍人士,还是使用 document.getElementById() 来直接面向 IE5+ 和 NN6 用户。我在这里提供一个函数,接受一个元素 ID 作为参数,能同时兼容这两种语法,并且还可以避免在更老版本的浏览器上触发错误:
function myFunc(elemID) { var elem = (document.getElementById) ? ?
document.getElementById(elemID)
: ((document.all) ? ?
document.all[elemID] : null); if (elem) { // act
on element } }
如果您不希望在您的函数中重复这么多的代码,则可以选择在脚本的上方建立全局的变量标志,来支持对元素引用进行分类:
var isW3C = (document.getElementById) ? true : false
var isAll = (document.all) ? true : false
然后在您为区分执行分支而建立起来的条件表达式中使用这些全局变量,如下所示:
function myFunc(elemID) {
var elem = (isW3C) ? document.getElementById(elemID) : ((isAll) ? ?
document.all[elemID] : null);
if (elem) {
// act on element
}
}
请注意为您的决策分支定义一个模式,使得最可能为 true(真)的条件首先被测试。例如,现在有这么多访问网站的用户使用的都至少是基本的 W3C DOM 浏览器,因此首先测试是否支持 W3C DOM。当然,IE5 同时支持两种元素引用风格是个事实,但是 W3C DOM 版本将会包含NN6用户,同时还有未来与标准兼容的浏览器。
对于那些作为事件处理器的函数,需要多个对象检测例程来处理事件模型的多样性和元素引用语法的不同,如下面的例子所示,这个例子引自同时支持三种事件模型这篇文章。
function functionName(evt) {
evt = (evt) ? evt : ((window.event) ? window.event : "")
if (evt) {
var elem
if (evt.target) {
elem = (evt.target.nodeType == 3) ? evt.target.parentNode : evt.target
} else {
elem = evt.srcElement
}
if (elem) {
// process event here
}
}
}
计划
设计对象检测的脚本通常要比设计直接识别浏览器的脚本花更多的时间。而且有一点,它要求您很好地掌握您的目标浏览器支持什么对象,属性,和方法。目前,Microsoft 和 W3C 的对象模型的规模已经很大了,再加上不同的操作系统和不同的浏览器版本对这些模型支持的级别也各不相同,使事情变得相当复杂。如果您不是拥有一个过目不忘的记忆,那可能会希望得到一个便利的参考资料,在形式上或者是一些最近的 JavaScript 相关的书籍,或者是一个兼容性图表。这些参考资料可以提供您所需要的对象,属性,和方法,以便用于测试。
对象模型使用得越多,就会发现越多对象检测的方法,发现可以把某个特性用作另外一些特性是否被浏览器支持的指示器。举例来说,每一个支持 document.getElementById() 方法的浏览器都必须保证支持 HTML 元素的 style(风格)属性。然而,在这个基础上,对于每一个风格表单属性的支持,就有比较广泛的差别了。因此,在支持 document.getElementById() 方法的执行分支中,您可以假定 style 属性已经得到支持了,然后直接进入那些不常见,或者非标准的风格表单属性是否存在的测试(比如仅在IE上得到支持的 style.pixelWidth 属性)。但是,请明智和有序地使用这种“关联支持”关系,并且只在对当前浏览器特性充分熟悉的情况上使用,否则您将会陷入到一些糟糕的习惯中去,正是这些习惯给上面说到的版本识别程序带来很多麻烦。如果您不确定是否关联支持,则可以通过显式的聚集对象检测,来提供没有错误的执行路径。
聚集对象检测的结果,可能会导致您的某些脚本分支需要很深的层次。请不要害怕,除非您的脚本必须以大量的反复循环来处理大量数据,否则,聚集的if结构对性能的影响是可以忽略不计的。
在编码的时候,您需要对包含检测分支的执行路径进行实际的考察。请注意,不但要在支持您设计的超酷特性的浏览器上考察脚本是如何执行的,也要看看在不支持这些特性的浏览器上进行检测会发生什么,看看您的脚本的确是否可以优雅地回退到较老版本的浏览器上。
测试
使用对象检测技术的主要优势在于它使我们可以摆脱对浏览器版本的担心,以及避免了直接处理一些未知因素(如不同的浏览器,或者未来的浏览器版本)。然而它不能帮助您省去在尽可能多的浏览器上测试代码的工作。这是因为一个浏览器声明它支持某一个特定的对象或者属性,并不意味着它所说的支持和其它浏览器的支持是一样的。
什么时候使用区分浏览器的方法
您可能偶尔会碰到一些问题,需要使用老式的区分浏览器版本的方法才能解决。一般来说,这些问题限制在已知的软件缺陷,或者某个对象在某个浏览器版本上有“可选行为”的时候。举例来说,假定在您尝试设定某个对象属性值时,Windows 版本的 MegaBrowser 3.05 浏览器无论当前渲染的是什么,总是报告这个属性的值为0,并产生一个错误,而该对象属性在其它浏览器上都支持良好,那么,您必须进行编码,绕过这个浏览器版本。您需要回到老的 navigator.userAgent 属性解析的方法,以便保护该浏览器的用户免受这个缺陷的影响。您不能指望预见每一个浏览器的缺陷,同样地,对象检测技术也不能帮助您做到这一点。
一些页面的设计者更喜欢使用专用于特定OS的风格表单或者内容,而不相信一个尺寸可以适合所有的场合。在这种情况下,根据操作系统的版本来进行程序分支是极为正常和恰当的。下面的脚本语句可以放到一个文档的HEAD部分,来为 Macintosh 用户连接一个专门用于 Mac 的风格表单定义文件(同时还使它在 Windows,Unix,和其它操作系统上也是最好的):
var isMac = navigator.userAgent.indexOf("Mac") != -1
if (isMac) {
document.write("<link rel=stylesheet type=text/css HREF=/css/mac.css>")
} else {
document.write("<link rel=stylesheet type=text/css HREF=/css/generic.css>")
}
值得付出努力吗?
在您对实现对象检测技术所需要的规划和思考过程感到愉快之前,您可能会对是否有必要花这么多额外的预备工作感到疑惑。如果您在过去因为新浏览器的出现,已有的脚本遭到破坏,并且经历过脚本的修改周期,那么,您就会渐渐认识到,这个目标之一为“用较少的维护量适应发展”的技术是如何减轻您的工作负担。当然,您可能没有意识到早些时候的艰苦工作是为了避免在随后的工作中出现一些令人头疼的问题。这使得对象检测似乎变成没有回报的工作,但是,这种方法不止可以使您在将来表现得好,更重要的是它使您避免在现在陷入糟糕的境地。
使用对象检测技术还可以带来好的编程实践。您需要知道在处理过程中需要的一些资源,并且需要考察脚本正常工作和不正常工作的执行路径。您也会逐渐认识到预见错误和优雅地处理错误在代码开发过程中的重要性。一旦您对对象检测技术的使用渐渐感到愉快的时候,您就会奇怪,为什么过去要为那些费解的浏览器识别方法而烦恼呢?