文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>JavaScript的对象检测

JavaScript的对象检测

时间:2007-03-02  来源:xiaobian

新版浏览器的发布速度比起早些时候可能是慢一些了,但是开发者仍然必须面对一系列令人头痛的,品牌和版本都各不相同的浏览 器,这些浏览器在 JavaScript 特性的支持方面存在差异。为了对付这个问题,脚本编程人员通常提供两个或者多个代码分支,使浏览器只执行由自身支持的语句组成的程序路径。由于目前存在的 浏览器版本变种如此之多,浏览器的识别--即通过考察 navigator 对象的属性来获得版本信息--在很大程度上变得不可管理。本文将展示针对这个问题的解决方案之一--对象检测技术--的细节,这个方案可以把 JavaScript 的开发人员从绝大部分判别浏览器版本的泥潭中解放出来。

浏览器识别方法的软肋

为了特定品牌以及/或者其不同版本(也可能是不同操作系统平台)的浏览器设定一个代码分支是基于如下的基本假设:即版本N的 X浏览器实现了一套已知的核心编程脚本语言和对象模型。脚本编程人员经常对这个假设加以扩展:即版本N及其之后版本的浏览器都会支持某个特定的功能。遗憾 的是,这些假设有几个问题。

让我们首先看一下浏览器的版本号。navigator.appVersion 属性可以返回一个 字符串,该字符串的开头部分是一个和 Mozilla 的版本相关联的数字。在早期的图形浏览器时代,绝大多数的商用浏览器都是在伊利诺伊大学(University of Illinois)的 NCSA Mosaic 浏览器引擎的基础上构建的。这些浏览器都会相当明确地告诉服务器(通过每一个服务器文件请求),它们是属于 Mozilla 家族的(不要和现代的 Mozilla.org 浏览器相混淆)。然而对于脚本编程人员来说,有一个不幸的事,即随着时间的推移,Mozilla 家族的版本号和实际浏览器产品的版本号之间的关联关系被弱化了,IE5 说它是 Mozilla 4.0;而 Netscape 6 则认为自己是 Mozilla 5.0。在这些时候,脚本必须解析包含更多细节信息的 navigator.userAgent 或者 navigator.appVersion 属性的值,才能精确地知道当前运行的产品的版本号。Netscape 6 中包含一个新的 navigator 对象属性,用于报告浏览器的产品版本号,然而您必须在新的代码分支中访问这些属性。总之,脚本编程者需要进行很多字符串的解析,才能产生一个指示浏览器版本的全局变量。

其次,您会碰到一个版本“大于等于”另一个版本,或者一个版本“等于”另一个版本的难题。在脚本编程者满怀热情地区别 Netscape 和 IE 的版本4浏览器的初始阶段,一些脚本程序只是简单的检查浏览器的版本号(先不考虑如何进行这个版本号的抽取)是否等于4。然而在 IE5 出现之后,这些脚本就不能使用 IE4 的特性了,因为5不等于4。反过来,那些自认为聪明的,在书写 Netscape 4 识别代码时使之可以接受版本4或者更新版本的人们,在某些时候也会陷入了困境,即当 NN6 出现的时候,那些依赖于 NN4 层的代码就会出现问题。

一方面,坚持采用“等于4”的人们无法考虑当前浏览器的问题。而另一方面,坚持“大于等于4”的人们又会碰到虽然很少出现 的,但又明显不是不可能的情况,因为浏览器厂商在推动人们采用 W3C DOM 标准的过程中,有时需要使对象模型特性(层对象)产生变化。虽然我们很容易预见,浏览器的后续版本肯定会出现,但是预测哪些特性在未来的浏览器中会过时或 者被删除,则不是容易的事。现在回过头来可以清楚地看到,在新版的浏览器开始扣响您的网站大门时,上述这两种浏览器的识别方法都需要紧急的代码修补。

最后,大多数浏览器识别代码容易忽略除了IE和NN之外的,同样支持脚本编程的浏览器-比如 Safari! Safari 提供了杰出的 W3C DOM 的脚本编程的支持,但是一个典型的浏览器识别程序会把它归入到“dumb and dumber”的范畴,因为对于 navigator.userAgent 属性值的字符串解析并不能揭示一个浏览器品牌是否有名,或者一个版本号是否足够高。

简单的对象检测

您可以通过一个被称为对象检测的技术来解决很多这样的问题。这个技术泛指基于当前的浏览器是否支持期望的对象,属性,或者方法来创建脚本执行分支的手法。

您可能已经看到对象检测技术在起作用了,只是没有认识到这种方法的强大功能。Netscape Navigator 2(所有OS版本)以及 IE3/Windows 都不支持用脚本编程来产生图像滚动效果,因为在 DOM中,IMG 元素并不被看成对象。但是后来的浏览器实现了 image 对象,并把一个页面上的所有 IMG 元素作为属于 document 对象的 image 对象数组公布出来。因此如果浏览器支持 image 对象,则 document 对象就有一个名为 images 的数组(集合),包含页面上的所有 image 对象。如果把对 image 对象进行操作的程序块包含在测试 document.images 数组是否存在的条件下面,早期版本的浏览器就可以简单绕过那些语句,不发生任何错误:

if (document.images) {
// image object manipulations here
}

这个语法是可以正常工作的,因为在不支持 image 对象的浏览器中,document.images 表达式的值是 undefined(没有定义)。而对于值为 undefined 的表达式,if 结构就将它等同于 false 来处理。

请进一步注意一下,这个无可非议的简单例子程序相当优雅。与鼠标相关的事件处理器在所有支持脚本编程的浏览器中都是可以工作 的,但是只有那些把 images 作为对象进行操作的浏览器才会尝试访问这些对象。这是一个优秀的例子,展示了如何使用脚本编程来为那些支持特定功能的浏览器上的页面增加额外的价值。

检测属性是否被支持

现在,随着对象模型变得越来越复杂,根据对象属性和/或者对象方法是否被支持来把代码进行条件分支的做法并不少见。举例来说,在 IE4 以及之后的浏览器上,document.body 对象包含一个名为 scrollTop 的属性,用来确定鼠标事件发生时,鼠标y轴在页面上的位置(不仅指页面的可视部分)。为了确保浏览器对 scrollTop 属性的支持,您可能需要使用下面的结构:

// maybe headed for trouble
if (document.body.scrollTop) {
// statements that work with scrollTop property
}

然而问题(脚本错误)还是出现了,出现在当这个if结构运行在不支持 document.body 对象的浏览器上时(比如 NN4 或者 IE3)。在对条件表达式进行求值时,这个“包含两个点”的表达式会引起脚本错误。为了避免这种错误,表达式必须首先测试 document.body 对象是否存在,然后再测试其属性是否存在:

if (document.body && document.body.scrollTop) {
// statements that work with scrollTop property
}

这行代码是可以正确工作的,因为当一个条件表达式中包含&& (AND)操作符时,最左边的表达式发生错误,将会导致整个表达式错误。因此,如果 document.body 不被支持(也就是说,它的值是 undefined),则整个条件表达式的值就等同于 false(假),而不必再尝试对第二个表达式进行求值。

检测方法是否被支持

页面中的函数也把自己公布为文档对象模型中的对象(其类型是 function)。类似地,对象方法也把自己公布为一个对象(在 IE4+/Windows 和 IE5/Mac; 上,其类型为 object;而在 NN3+ 上,其类型为 function)。

由于对象方法(方法名中不能有圆括号)在求值时会返回一个值,所以您可以很轻松地测试某个对象方法是否被浏览器支持。您可以在条件表达式中引用一个对象方法,就好象引用对象属性一样。举例来说,假定您想看看浏览器是否支持 document.getElementById() 方法,则可以用下面的结构来进行测试:

if (document.getElementById) {
// supports the method -- go for it!
}

这种验证方法只在几个早期的浏览器上会不起作用,特别值得一提的是在 NN2,IE3/Windows,以及版本5之前的 IE/Mac,问题出现在当被测试的对象方法真的被(不支持方法检测的)浏览器支持的时候:在条件表达式中测试一个现有的方法是否存在, IE 上会导致脚本错误,而在 NN2 上则会导致求值错误。支持方法检测的浏览器的最小公分母是那些以某种方式支持 W3C DOM 的浏览器集合。换句话说,为了最大程度的兼容性,您应该对那些属于 W3C DOM 的方法进行测试。因为对于不同的 W3C DOM 对象和方法的支持在不同版本的浏览器中变化比较大,这个测试可以帮助脚本优雅地绕过潜在的雷区。

一个陷阱及其规避方法

在使用属性检测之前,您需要知道正在被检测的属性的数据类型和缺省值。如果浏览器支持的属性值为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'>")
}

值得付出努力吗?

在您对实现对象检测技术所需要的规划和思考过程感到愉快之前,您可能会对是否有必要花这么多额外的预备工作感到疑惑。如果您 在过去因为新浏览器的出现,已有的脚本遭到破坏,并且经历过脚本的修改周期,那么,您就会渐渐认识到,这个目标之一为“用较少的维护量适应发展”的技术是 如何减轻您的工作负担。当然,您可能没有意识到早些时候的艰苦工作是为了避免在随后的工作中出现一些令人头疼的问题。这使得对象检测似乎变成没有回报的工 作,但是,这种方法不止可以使您在将来表现得好,更重要的是它使您避免在现在陷入糟糕的境地。

使用对象检测技术还可以带来好的编程实践。您需要知道在处理过程中需要的一些资源,并且需要考察脚本正常工作和不正常工作的 执行路径。您也会逐渐认识到预见错误和优雅地处理错误在代码开发过程中的重要性。一旦您对对象检测技术的使用渐渐感到愉快的时候,您就会奇怪,为什么过去 要为那些费解的浏览器识别方法而烦恼呢?

相关阅读 更多 +
排行榜 更多 +
辰域智控app

辰域智控app

系统工具 下载
网医联盟app

网医联盟app

运动健身 下载
汇丰汇选App

汇丰汇选App

金融理财 下载