说说浏览器嗅探与特性探测
浏览器嗅探
第一种技巧是分析User Agent来获取,比如jQuery1.3.1的core.js中:
// Use of jQuery.browser is deprecated.
// It's included for backwards compatibility and plugins,
// although they should work to migrate away.
var userAgent = navigator.userAgent.toLowerCase();
// Figure out what browser is being used
jQuery.browser = {
version:(userAgent.match(/.+(?:rv|it|ra|ie)[/: ]([d.]+)/) || [0,'0'])[1],
safari:/webkit/.test(userAgent),
opera:/opera/.test(userAgent),
msie:/msie/.test(userAgent) && !/opera/.test(userAgent),
mozilla:/mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent)
};
注意前面的注释,jQuery.browser已经被deprecated.
第二种技巧是根据浏览器特性来探测。比如Mootools 1.2的源码中:
if (window.opera) Browser.Engine = {
name: 'presto',
version: (document.getElementsByClassName) ? 950 : 925};
else if (window.ActiveXObject) Browser.Engine = {
name: 'trident',
version: (window.XMLHttpRequest) ? 5 : 4};
else if (!navigator.taintEnabled) Browser.Engine = {
name: 'webkit',
version: (Browser.Features.xpath) ? 420 : 419};
else if (document.getBoxObjectFor != null) Browser.Engine = {
name: 'gecko',
version: (document.getElementsByClassName) ? 19 : 18};
Browser.Engine[Browser.Engine.name] =
Browser.Engine[Browser.Engine.name + Browser.Engine.version] = true;
全部代码请查看Mootools源码中的Browser.js. 这看起来很妙。
上面两种方法孰优孰劣?也许大部分看官会为第二种方法喝彩,理由是User Agent很容易被伪造,同时随着浏览器升级,厂商日后会增加User Agent. 但是,这两条理由真的就能让第二种方法看起来更好?
- User Agent容易被伪造。事实的确如此。然而,伪造的目的是什么?表面上看是为了军备竞赛,比如绝大部分浏览器都在User Agent开头将自己声明为Mozilla引擎,还有Opera干脆在User Agent上加入MSIE. 一片混乱的世界!除了厂商,还有些高级用户会利用浏览器插件来主动更改User Agent. 厂商的目的是,让开发的浏览器能显示更多网页。高级用户的主要目的是出于测试。可以看出来:User Agent无论多么混乱,本意都是为了能显示更多网页,并没什么不妥。
- User Agent日后会增加。这一条不多说,根本站不住脚。随着浏览器的升级,第二种方法里的特性更有可能发生变化。
上面两种方法有个共同点:都是通过某种途径得到浏览器种类,使用方式大同小异:
if(jQuery.browser.msie) {
// do something
} else if(jQuery.browser.opera) {
// ...
}
这是很典型的浏览器嗅探(Browser Sniffing),除了在有限的场景(比如访问统计程序),在其它代码中已经被ppk等牛人们批判得快进入十九层地狱了。
结论很明显:上面两种方法,Mootools表面上看更“先进”一点,但实际上和jQuery.browser一样,都在诱惑使用者书写嗅探代码,半斤八两,只是平手。
特性探测
来看jQuery 1.3推荐的support方式:
jQuery.support = {
// IE strips leading whitespace when .innerHTML is used
leadingWhitespace: div.firstChild.nodeType == 3,
// Make sure that tbody elements aren't automatically inserted
// IE will insert them into empty tables
tbody: !div.getElementsByTagName("tbody").length,
// Make sure that you can get all elements in an <object> element
// IE 7 always returns no results
objectAll: !!div.getElementsByTagName("object")[0]
.getElementsByTagName("*").length,
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
htmlSerialize: !!div.getElementsByTagName("link").length,
// Get the style information from getAttribute
// (IE uses .cssText insted)
style: /red/.test(a.getAttribute("style"))
// ...
上面只是代码片段,更详细的实现请阅读support.js. 使用方式如下:
if(jQuery.suport.noCloneEvent) {
// do somthing
}
这种方法叫做特性探测(Feature Detection),在有些地方也被称之为对象探测(Object Detection)。与浏览器嗅探相比,特性探测能减少代码冗余,同时让代码更健壮。
从理论上讲,特性探测能让我们面向特性编程,而无需考虑具体的浏览器,这是一种观念上的革新。然而,仔细查看jQuery.support中的注释,会发现这些特性和浏览器紧密相关:
...
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
htmlSerialize: !!div.getElementsByTagName("link").length,
// Get the style information from getAttribute
// (IE uses .cssText insted)
style: /red/.test( a.getAttribute("style") ),
// Make sure that URLs aren't manipulated
// (IE normalizes it by default)
hrefNormalized: a.getAttribute("href") === "/a",
// Make sure that element opacity exists
// (IE uses filter instead)
opacity: a.style.opacity === "0.5",
...
注释中的IE好刺眼。也就是说,下面这段代码:
if(jQuery.browser.msie) {
// do something 1
// do something 2
// do something 3
}
改用support后,得写成下面这样:
if(!jQuery.support.boxModel) {
// do something 1
}
if(!jQuery.support.opacity) {
// do somthing 2
}
if(!jQuery.support.style) {
// do something 3
}
可以看出,特性探测的粒度比浏览器嗅探更小更细。表面上看起来,这样会让代码更繁琐更复杂,但如果长远想:
- 假设IE从某个版本开始支持opacity同时依旧支持filter,用特性探测写的代码除了多了点冗余代码,不需要修改任何地方。但如果用的是浏览器嗅探,不修改代码,可能会导致效果不对。
- 市场上出现了一种新的浏览器,支持opacity但不支持style. 用特性探测写的代码可以很好的工作。用浏览器嗅探写的代码,则很可能会出问题,需要针对新浏览器做些代码上的修改。
上面第一点表明,用特性探测更容易让代码保持健壮。
上面第二点也是需要我们认真考虑的。Chrome的诞生,iPhone上的Safari, 各种浏览器只会越来越多,越来越丰富。特性探测最大的优点是:让我们忘掉浏览器,只需关心各种有差异的特性即可。比如opacity,用特性探测,我们只需记得目前还有浏览器不支持,我们需要注意就够了,而不需要分清究竟是哪个浏览器不支持opacity. 当然,前提是需要一份维护良好的特性列表,以及特性不支持时的备选方案。
目前jQuery.support还非常粉嫩,甚至很不好用。但无论如何,jQuery对特性探测的尝试和推行非常值得赞许。很期待一份在各个浏览器中有差异的特性列表的出现,感觉要等到那时,特性探测技术才有可能真正融入千万网站的代码中。
神奇的世界
最后说下JS界的tiny detecting:
ie = '\v'=='v';
ie = !!top.execScript;
ie = /*@cc_on!@*/!1;
ie8 = !!window.XDomainRequest;
IEVersion = 0/*@cc_on+ScriptEngineMajorVersion()@*/;
ff = /a/[-1]=='a';
ff3 = (function x(){})[-5]=='x';
ff2 = (function x(){})[-6]=='x';
safari=/a/.__proto__=='//';
chrome=/source/.test((/a/.toString+''));
opera=/^function (/.test([].sort);
上面的代码很神奇,更重要的是:It works! 然而,无论多么神奇,上面这些都是浏览器嗅探的某种实现方案,并不值得推荐。
很赞同Ajaxian上 IE=’\v’==’v’ 这篇文章中jaffathecake的评论:
Actually, I think the UA string is more reliable here. The purpose of the UA string is to identify the user agent, on purpose. Whereas it’s just coincidence that the vertical tab thing identifies IE.
The UA is sometimes spoofed, yes, but there the browser is actively lying about what it is. Whereas with the /v behaviour, that could appear in non-ie browsers by accident, or be fixed in IE.
如果真的需要探测浏览器,根据UA比根据特性来检测浏览器更可靠。随着时间的推移,特性很可能会同化,但UA不会。
小结
- 特性探测的粒度更细,最大的优点是:将关注点从浏览器转移到有差异的特性上。忘掉浏览器,才能兼容更多浏览器。
- 小心伪特性探测:比如
if(window.ActiveXObject) { // 一大堆针对ie但和ActiveXObject没任何关系的代码 } - 如果要检测浏览器类型,UA比特性更可靠。

January 30th, 2009 on 18:15
雕兄的文章都很精品!
January 30th, 2009 on 22:15
看看此文 :http://blog.davglass.com/2009/01/browser-sniffing-vs-object-detection/
尤其是最后一段:
January 30th, 2009 on 22:30
我觉得现在jQuery.support确实不够健壮。无法完全抛弃原先的jQuery.browser,尤其有些时候要针对IE6加上对应的CSS,而这些都不是JS能侦测到的。到不如直接根据UA判断是不是IE6呢。当然有些时候可以直接写HACK或者用条件注释解决,但总有那么点时候……ie和其他浏览器就不一样……且只能用js动态修改……唉……
February 1st, 2009 on 20:23
winter: 浏览器具体类型检查只适用于那种不可检查的差异 比如内存泄露
leave a reply