曾经,浏览器类型嗅探技术被戏称为javascript程序员的“股票交易”。假若咱们知道一些可以在IE5中生效但却没法在Netscape4运行的技术,咱们首先作浏览器类型嗅探,而后根据不一样的浏览器的兼容性来写代码。好比:javascript
if(navigator.userAgent.indexOf('MSIE 5') != -1) { //we think this browser is IE5 }
然而,各个浏览器厂家竞争日益激烈,他们在user-agent字段里增添额外的值,来确保该浏览器为该厂家拥有全部权。这是Mac版本Safari5的user-agent:html
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.59.10 (KHTML, like Gecko) Version/5.1.9 Safari/534.59.10html5
这个字符串能够匹配“safari”、“webkit”与“KHTML”(Webkit构建基础——Konqueror codebase),可是它也可以匹配到“Gecko”(firefox的渲染引擎),还有“Mozilla”(因为历史缘由,几乎全部浏览器都声明为Mozilla)java
增长这些值的目的是为了规避浏览器嗅探技术。假如一个脚本声明只有firefox可以处理一个特别的功能,它也有可能在其余浏览器如safari可以运行。另外不要忘记用户本身也能够改变user-agent——好比我把个人浏览器设置成“ Googlebot/1.0”,因此我能够访问站长认为仅仅搜索引擎爬虫能够访问到的内容!程序员
所以,一段时间以后,这种浏览器嗅探技术成为一种没法实现的困扰,已经大大地失去了做用,另外一种更好的方法闪亮登场——特性检测。web
特性检测简单地检查咱们须要用到的特性。举个栗子,若是咱们须要getBoundingClientRect(获取元素相对浏览器可见视窗的位置),那么要作的重要事情就是浏览器是否支持这个属性;因此。咱们不该该检测浏览器类型,而是作特性自己的检测:ajax
if(typeof document.documentElement.getBoundingClientRect != "undefined") { //the browser supports this function }
不支持该特性功能的浏览器会返回“undefined”类型,因此不会进入条件。即便不经过咱们在指定的浏览器测试这脚本,咱们也知道它要么运行成功,要么静默失败。chrome
可是,事实上——特性检测也不是彻底可依赖的——他们有时候也会失败。让咱们来看一些例子,来了解咱们如何处理每一个事例。浏览器
或许,作特性检测失败最广为认知的事例是,在IE中检测ActiveXObject来作ajax请求。安全
ActiveX 属于晚绑定对象,具体意思就是在尝试用ActiveX以前,你是没法肯定
浏览器是否支持这个特性。因此,若是用户禁止了ActiveX,下面的代码会抛出错误:
if(typeof window.ActiveXObject != "undefined") { var request = new ActiveXObject("Microsoft.XMLHTTP"); }
为了解决这个问题,咱们须要作异常处理——根据不一样状况尝试实例化对象,捕捉失败:
if(typeof window.ActiveXObject != "undefined") { try { var request = new ActiveXObject("Microsoft.XMLHTTP"); } catch(ex) { request = null; } if(request !== null) { //... we have a request object } }
属性映射常常用来测试浏览器对HTML5 API属性的支持状况。好比,经过寻找draggable属性,检查带有[draggable="true"]的元素支持** Drag and Drop API**:
if("draggable" in element) { //the browser supports drag and drop }
问题是,IE8或者更早的版本,自动创建了全部HTML属性到DOM属性的映射关系。这就是getAttribute在老版本IE是如此糟糕的缘由,由于它根本不返回HTML属性,而是返回一个DOM 属性。
这意味着,若是咱们用已经存在属性的元素:
<div draggable="true"> ... </div>
这在IE8或更早的版本中,进行draggable检测会返回true,即便这些浏览器并不支持draggable属性。
HTML属性(attribute)能够是任意的:
<div nonsense="true"> ... </div>
在IE8或更早的版本,结果可想而知,返回为true
"nonsense" in element
解决方案,检测没有属性(attribute)的元素,最安全的方法就是建立新的元素:
if("draggable" in document.createElement("div")) { //the browser really supports drag and drop }
你可能遇到过这样的代码用来检测可触控设备:
if("ontouchstart" in window) { //this is a touch device }
大多数触控设备在触发click事件以前故意设置了一个延迟(一般接近300ms),以便于元素可以触发double-tapped而不是进行点击。可是这样会使应用感到反应迟缓或者无响应。因此,开发者有时采用特性检测来管理不一样事件:
if("ontouchstart" in window) { element.addEventListener("touchstart", doSomething); } else { element.addEventListener("click", doSomething); }
然而,这个条件处理来自于一个错误的判断——由于设备只要支持touch,touch事件就会被采用。可是触控屏幕的笔记本该怎么办?用户可能触控屏幕,或者可能用鼠标或触控板。上边的代码没法处理这种状况,因此用鼠标点击根本没有做用。
解决方法就是根本不用检查浏览器对事件类型的支持——取而代之,绑定这些事件包括touch与click,而后采用preventDefault来阻止touch来产生click事件
element.addEventListener("touchstart", function(e) { doSomething(); e.preventDefault(); }, false); element.addEventListener("click", function() { doSomething(); }, false);
这是能够容忍的痛苦事情,可是有时候这不是咱们须要的特性的问题——某些浏览器声明支持一些特性,但实际却不能生效。最近的例子就是Opera12中的setDragImage()(可拖拽对象的方法dataTransfer)
这里特性检测失败就是由于Opera12仅是对外声明支持,却没法真正的实现;即便异常处理也不会有做用,由于它不会抛出任何错误。它仅仅是不能运行:
//Opera 12 passes this condition, but the function does nothing if("setDragImage" in e.dataTransfer) { e.dataTransfer.setDragImage("ghost.png", -10, -10); }
或者,某个浏览器开发了某个特性,可是却有BUG。
面对上述问题,咱们不得不思考:什么才是检测浏览器最安全的方法?
我有两点建议:
1. 采用专有对象(proprietary object)测试 优先于 navigator信息
2. 采用排除法,即不包括该特性的浏览器来作特别处理,而不是囊括具备某特性的全部浏览器来作特别处理
好比: opera12或者更早的版本能够检测到window.opera对象,因此咱们能够这样检测除了opera以外的其余浏览器对draggable的支持状况:
if(!window.opera && ("draggable" in document.createElement("div"))) { //the browser supports drag and drop but is not Opera 12 }
更好的方式,咱们加上浏览器专有对象的检测:
if(window.opera) { //Opera 12 or earlier, but not Opera 15 or later } if(document.uniqueID) { //any version of Internet Explorer } if(window.InstallTrigger) { //any version of Firefox }
专有对象能够采用组合的方式:
if(document.uniqueID && window.JSON) { //IE with JSON (which is IE8 or later) } if(document.uniqueID && !window.Intl) { //IE without the Internationalization API (which is IE10 or earlier) }
咱们已经提过userAgent字符串是不可依赖的,可是vendor字段实际上仍是可判断的,能够用来检测是chrome仍是safari:
if(navigator.vendor == 'Google Inc.') { //any version of Chrome } if(navigator.vendor == 'Apple Computer, Inc.') { //any version of Safari (including iOS builds) }
以前,我喜欢测试检查对象与特性的各类写法。好比,下面的写法是最近比较经常使用的:
if("foo" in bar) { }
过去咱们不能采用这种写法是由于IE5时代的浏览器会抛出语法错误;可是如今咱们不在考虑这个问题,由于咱们不须要在支持这些浏览器。
在本质上,它与下面的写法是等价的:
if(typeof bar.foo != "undefined") { }
由于JS能够进行隐式转换,因此咱们还能够这样写:
if(foo.bar) { }
但这种写法也会有潜在的问题:foo.bar 为空字符串或者布尔值false或者数值0的时候,结果就与咱们的预期有差距。举个例子,*style.maxWidth * 属性有时会被除了IE6的浏览器运用,咱们应该这样检测:
if(typeof document.documentElement.style.maxWidth != "undefined") { }
maxWidth属性只会在浏览器支持而且做者设定了该值转换为true,因此咱们这样写检测就会失败:
if(document.documentElement.style.maxWidth) { }
总结一下主要规则 : 自动类型转换相对对象与函数是安全可信赖的,可是针对字符串与数值却不值得信赖。
既然已经说到这:若是你可以安全利用它,那就去作吧。由于它在现代浏览器一般比较快。
更多内容,请关注:Automatic Type Conversion In The Real World.
原文地址: http://www.sitepoint.com/javascript-feature-detection-fails/