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

【2010.11.29 更新】
之前的 IE8 检测方式容易被“伪造”,
改为检查 Image 对象是否存在 prototype 的方式。(via)
另外有朋友说 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 的判别特征换成了引擎特性 [via],
这样你就能看出国内哪些浏览器是从 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》 (推荐)

标签: Firefox, User-Agent, IE, Opera, Safari, Chrome

已有 47 条评论

  1. 高清mv下载 高清mv下载

    看不太懂这些特征...

  2. 颂赞 颂赞

    听过你几次演讲,不错。学习了

  3. 赵弟栋 赵弟栋

    需要哪个功能时判断浏览器支不支持该功能即可
    不宜通过支不支持某个功能断定是否某浏览器某版本哦

  4. john john

    看不大明白

  5. detox foot bath detox foot bath

    写得很好,学习了

  6. 宁波网站建设 宁波网站建设

    额,我不怎么样IE的浏览器,我用的是谷歌的,还是不错的。

  7. Eye Nuts Eye Nuts

    谷歌的蛮好的!~最近用的比较爽!~~

  8. wo_is神仙 wo_is神仙

    加个检测iPad的吧

    1. Chao QU Chao QU

      iPad 内置的就是 Webkit 内核,
      不容易找到可以区别并无法伪造的特征。
      请使用传统方法来检测吧。

  9. 大大的小蜗牛 大大的小蜗牛

    IE9出来后,又多了一件事。

  10. asins asins

    var ie = function(v, p, needle, undef) {

    needle = p.getElementsByTagName('br'); while(v > 0){ p.innerHTML = ''; if(needle[0]) return v; } return v;

    }(10, document.createElement('p'));

    alert(ie);

    以前射雕的一个PPT里写的,改进了下,用来判断版本还是相当不错的,用着踏实。
    只是对于国内那些装马夹的浏览器不好讲,没去测过。

    1. Chao QU Chao QU

      @asins
      这个判断稍长,
      而且依赖 DOM ,
      不能写到头部。

      当然,
      我所列举的 IE6 判断代码也依赖 DOM ,
      所以适应性不够强。

      今天中午还和玉伯聊了这个话题。

      浏览器特征虽然难找和晦涩,
      但结果肯定比 UA 要准。

      :)

  11. air nailer air nailer

    这么有研究,一定要顶一个

  12. 岁月无痕 岁月无痕

    我听他们说IE 9非常快,是的,加载非常快。
    但是,我现在主要用firefox,所以我最想知道的是IE 9是否支持网银?

  13. 文学社 文学社

    研究的真深入 如果我有你一半的功力就满足了

  14. 家用制氧机 家用制氧机

    谢谢分享!!!

  15. LifeCandy LifeCandy

    进来学习一下。很高深的问题哦。

  16. sanitary ware fittings sanitary ware fittings

    探测的蛮详细的,支持一下

  17. New Balance Shoes New Balance Shoes

    加油哦

  18. 陈维国博客 陈维国博客

    只用的习惯IE6.0,杯具啊

  19. OoSleePing OoSleePing

    对于IE8的侦测有一个问题,在IE8调试开启IE7模式的时候!!window.XDomainRequest也会返回true

    1. Chao QU Chao QU

      @OoSleePing
      谢谢您的报告,
      IE8 的识别目的主要是探测是否为 IE8 本身,
      是否为兼容模式可以使用 documentMode 去进一步判断。

  20. 儿童英语学习 儿童英语学习

    写得太精彩了,学习了!

添加新评论