浏览器检测

曾经,浏览器类型嗅探技术被戏称为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

或许咱们还能够作...

可是,事实上——特性检测也不是彻底可依赖的——他们有时候也会失败。让咱们来看一些例子,来了解咱们如何处理每一个事例。浏览器

ActiveX object

或许,作特性检测失败最广为认知的事例是,在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
  }
}

HTML属性( attributes )与DOM属性的映射(properties)

属性映射常常用来测试浏览器对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事件就会被采用。可是触控屏幕的笔记本该怎么办?用户可能触控屏幕,或者可能用鼠标或触控板。上边的代码没法处理这种状况,因此用鼠标点击根本没有做用。

解决方法就是根本不用检查浏览器对事件类型的支持——取而代之,绑定这些事件包括touchclick,而后采用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/

相关文章
相关标签/搜索