浏览器的特征探测

转载自:http://www.quchao.com/entry/detect-browser-by-features/

作者:屈超(Chappell.Wat)

【2010.12.08 更新】
抱歉,
用原生 IE9 测试后发现 IE9 的判断代码已失效,
研究将继续……

【2010.11.29 更新】
之前的 IE8 检测方式容易被“伪造”,
改为检查 Image 对象是否存在 prototype 的方式。
另外有朋友说 IE8 的探测在 IE7 模式下也通过,
我想说这是我有意为之:
目的就是要探测浏览器本身的真实版本,
具体运行于哪种模式可以通过 doc.documentMode 来获得。

【2010.11.17 更新】
IE9 不知从哪个 Pre 版开始,
又恢复了之前更新时所提到的
非空数组字面量的最后一个元素缺失的问题,
也就是说这个 Bug 又得以“重现”了。
我个人推断是太多线上应用依靠这个 Bug 来识别 IE ,
因此开发团队不得已而为之。
但不管怎样,
本文还得继续。
目前利用了 IE9- 里 toFixed 方法在特定情况不会四舍五入的 Bug 来判定,
具体代码请见正文。

【2010.08.13 更新】
增强了 IE6 的区分能力。
当然更可以使用 IE 自己的条件编译,
只是有的压缩器支持度有限,
不便于日常开发。

【2010.04.18 更新】
更加严格地区分出了 IE 9,
增强了 Opera 的区分能力,
并将 Chrome 的判别特征换成了引擎特性,
这样你就能看出国内哪些浏览器是从 Chrome 的内核改过来的了,
大家可以动手试试。 :)

【2010.04.17 更新】
IE 9 在我看来改进非常大:
有我所关心的对 Mutation Events 的支持,
还有事件模型的 W3C 化 等等……
本文中我就不多冗述,
有机会再另文探讨。
由于在项目中大量使用特征判断,
IE 9 在这方面最显著的变化是:
之前那则“最短的 IE 判断法”已经失效,
一起被修复的还有一直伴随 IE 的数组 Bug:
非空数组字面量的最后一个元素缺失的问题(即:[null,]),
因此现如今的最短记录保持者(6字节的 !-[1,])也已然失效。(而且不支持 CC 压缩)
你也不要尝试使用 addEventListener 方法来判别,
因为 IE 9 已经支持了。(事件的改进非常大,这很好)
所以说 IE 已经越来越靠近标准,
如果在项目中有对 IE 进行特殊处理的代码,
你可能需要在 IE9下重新检查一下了。

由于我个人依旧是 XP 的忠实拥趸,
而 XP 又很悲剧地无法安装 IE 9,
所以一些测试是在 Haitao Jia 同学的协助下完成,
在此表示感谢。
也正因为如此,
我没有进行覆盖面太广的测试。
不过目前所发现的唯一“幸存”下来的 IE 系列 Bug 依旧与刚提到的数组 Bug 有关,
微软修复了字面量里的 Bug 却忘了当字符串被 split 成数组后却涛声依旧啊。
虽然判断起来稍微麻烦了点,
但不管怎样这是我目前发现的仅剩不多地可以用来判断 IE 全系列的代码:

!','.split(/,/).length

具体的代码请拖至文末。

【2010.03.18 更新】
IE 8 也支持 window.DataTransfer 这个拖拽时方法,
因此判断 gecko 内核的条件有所改动,
你可以在原有判断条件上排除 IE8 ,
或者选择 window.mozInnerScreenX 这类由 FF3.6 开始提供的 gecko 特有属性,
(相对应的获取纵坐标属性 window.mozInnerScreenX )
还有用以检测某个节点是否满足某个选择器规则的方法 node.mozMatchesSelector (很实用)

【2010.01.21 更新】
Google 的 Closure Compile 会将 IE 的判断代码“压缩”成:

!+"/u000b1"

要么压缩后替换回 !+"/v1",
要么换其它更安全的方法来判断 IE 。

【2010.01.03 发表】
在撰写此文之前,陈成 告诉我 Nicholas C. Zakas 大师几天前刚好写了一篇名为
《特征探测并非浏览器探测 (Feature detection is not browser detection) 》
的文章。
文章里“深刻”批判了MooTools所使用的特征探测法,
但真正令人信服的理由似乎在文中也并无体现,
只是说 MooTools 因 Firefox 3.6 的变化而被迫发布了一次升级,
然后说:

“当浏览器(功能)愈发地接近彼此,想从“特征”去区别它们将变得越来越困难和危险。
(As browsers grow closer together, looking at “features” to separate them will become more difficult and risky.)”

当然啦,
不仅是 Javascript ,
服务端想要统计客户端也必须依靠 User-Agent  (以下简称 UA),
而对于 UA Spoofs ,
尼古拉斯的看法是:

“你必须永远尊重浏览器所告知你的 UA 。
(You should always honor exactly what the browser is reporting as a user-agent.)”

因此从前后端统一的角度,
我个人还是赞同这一观点的。

但从另一方面看,
现在浏览器内核虽稍显得固定,
但集成多种内核出来闯荡江湖的浏览器也不少,
而且它们在中国的占有率都不是一般的高,
(但它们对 UA 的管理则不是一般的糟糕)
我想这是尼大师所没有料到的。
(值得一提的是马桶3的 web-kit 模式这次提供了 UA 特征符)

因此我觉得对于特征探测不可一棒子敲死,
而对于 UA 嗅探法也不能一味地捧上天,
能够准确判断出浏览器继而进行正确的 Hack 来确保完整体验才是王道。

呃……
写了这么多,
完全是针对尼大师的新文有感而发,
我预想中的正文从这里开始——

由于 Firefox 3.6 产生了巨大的变化 ,
(当然不仅是 Javascript 这个层面,
也比如旧的 chrome 注册文件 contents.rdf 也被废止等等)
我们理应将 3.6 版本作为 Firefox 的一个里程碑版本来对待。
对于本文而言,
也就是传说中用来区别 Firefox 的诸多特征已经消失:
比如最令人熟知的 window.getBoxObjectFor() ,
再比如 /a/[-1] == 'a' 这个 trick 。
因此我们必须找一个新的特征来填上这个漏洞,
(想从 Firefox 1.0 找一个延续至今的特征极为困难)
查阅文档后你刚好可以找到一个从 3.6 开始
被 Firefox 用来保存拖拽时数据的方法 window.DataTransfer() , (经查 IE8 同样支持)
被 Firefox 提供用以获取可视区域相对窗口横/纵坐标的属性 window.mozInnerScreenX/Y ,
而且经测试在最新的 Firefox 3.7a1pre nightly 也得到支持。

因此经过整理汇总,
我个人比较赞同的特征探测方法如下:

(function (win, doc) { var isIE = !+'/v1', // alt: !!-[1,] isIE6 = isIE && !('maxHeight' in doc.body.style), isIE8 = isIE && 'prototype' in Image, isIE7 = isIE && !isIE6 && !isIE8, //isIE9 = isIE && .1 === +(.09).toFixed(1), isFF = !!doc.getBoxObjectFor || 'mozInnerScreenX' in win, // gecko isOP = !!win.opera && !!win.opera.toString().indexOf('Opera'), isOP9 = /^function /(/.test([].sort), isWK = !!win.devicePixelRatio, // web-kit isSF = /a/.__proto__ == '//', // safari isCR = /s/.test(/a/.toString); // chrome })(window, document);

其中值得一提的是 web-kit 内核,
window.devicePixelRatio() 能区分出所有的 web-kit based 浏览器,
其中包括 Maxthon 3 的极速模式和其它尚未发布的类似浏览器。
Chrome 比 Safari 多一个 window.MessageEvent,
但要注意排除 Firefox。

最后呢,
我还整理了 Firefox 重大里程碑版本的特征判断法,
不建议被纳入通用的判断方法里,
但是如果你刚好被这些版本的差异所困扰时,
它们应该能有用:

(function (win) { var aboveFF36 = 'mozInnerScreenX' in win, // alt: !!document.body.mozMatchesSelector aboveFF35 = !!Object.getPrototypeOf, aboveFF2 = !!win.globalStorage; aboveFF3 = upperFF2 && !!win.MessageEvent, // alt: 'reduce' in Array aboveFF15 = 'some' in Array; })(window);

其中 3.0 和 3.6 是我个人觉得变化最大的两个里程碑版本,
指不定什么时候你就能用到。
另外,
Firefox 的重大版本变化几乎总跟随着 Javascript 的版本升级,
因此对于 3.0 版本你还可以用 'reduce' in Array 来判断,
当然前提是你没有对 Array 进行扩展。

Ref:
《Javascript浏览器判断终极技巧 》
《JavaScript 判断浏览器类型及版本 》
(以上两文总结得都很不错,
只是 Firefox 和 Safari 的部分需要更新)
《Detecting browsers javascript hacks 》 (推荐)

你可能感兴趣的:(浏览器的特征探测)