JavaScript功能检测失败时

曾几何时,浏览器检测是JavaScript程序员的库存商品。 如果我们知道某些功能在IE5中有效 ,而在Netscape 4中则无效,那么我们将测试该浏览器并相应地存储代码。 像这样:

if(navigator.userAgent.indexOf('MSIE 5') != -1)
{
  //we think this browser is IE5
}

但是当我第一次加入这个行业时,军备竞赛已经进行得很顺利! 供应商在用户代理字符串中添加了额外的值,因此它们似乎是竞争对手的浏览器,以及他们自己的浏览器。 例如,这是适用于Mac的Safari 5:

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.10

这将与SafariWebkit以及KHTML (Webkit所基于的Konqueror代码库)的测试相匹配; 但它也匹配Gecko (这是Firefox的渲染引擎),当然还有Mozilla (由于历史原因 ,几乎每个浏览器都声称是Mozilla)。

添加所有这些值的目的是为了规避浏览器检测 。 如果脚本假定只有Firefox可以处理特定功能,则它可能会排除Safari,即使它可能会起作用。 并且不要忘记,用户自己可以更改其用户代理-众所周知,我将浏览器设置为Googlebot / 1.0 ,因此我可以访问网站所有者认为仅可用于爬网的内容!

因此,随着时间的流逝,这种浏览器检测已成为一种不可能的纠结,并且在很大程度上已经不再使用,而被功能更好的东西所取代。

特征检测只是测试我们要使用的特征。 例如,如果我们需要getBoundingClientRect (以获取元素相对于视口的位置),那么重要的是浏览器是否支持它 ,而不是哪种浏览器; 因此,我们没有测试支持的浏览器,而是测试了功能本身:

if(typeof document.documentElement.getBoundingClientRect != "undefined")
{
  //the browser supports this function
}

不支持该功能的浏览器将返回"undefined"类型,因此不会通过该条件。 无需我们在任何特定的浏览器中测试脚本,我们都知道该脚本将正常运行,或者无提示地失败。

还是我们……?

但问题是- 特征检测也不是完全可靠的 -有时它会失败。 因此,现在让我们看一些示例,看看如何解决每种情况。

ActiveX对象

功能检测失败的最著名示例可能是测试ActiveXObject在Internet Explorer中发出Ajax请求

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属性映射到DOM属性

属性映射通常用于测试对HTML5属性附带的API的支持。 例如,通过查找draggable属性,检查具有[draggable="true"]的元素是否支持拖放API

if("draggable" in element)
{
  //the browser supports drag and drop
}

这里的问题是IE8或更早版本会自动将所有 HTML属性映射到DOM属性。 这就是为什么getAttribute在这些旧版本中如此混乱的原因,因为它根本不返回任何属性,而返回DOM属性。

这意味着如果我们使用已经具有属性的元素:

<div draggable="true"> ... </div>

然后,即使他们不支持IE8或更早版本,可拖动测试也将返回true。

该属性可以是任何东西:

<div nonsense="true"> ... </div>

但是结果将是相同的-IE8或更早版本将为("nonsense" in element)返回true。

在这种情况下,解决方案是使用不具有属性的元素进行测试 ,而最安全的方法是使用已创建的元素:

if("draggable" in document.createElement("div"))
{
  //the browser really supports drag and drop
}

关于用户行为的假设

您可能已经看到了用于检测触摸设备的如下代码:

if("ontouchstart" in window)
{
  //this is a touch device
}

大多数触摸设备会在触发click事件之前实施人为延迟(通常约为300 ms ),这样一来,即使没有点击元素,也可以双击元素。 但是,这会使应用程序感觉迟钝和无响应,因此开发人员有时会使用该功能测试来分叉事件:

if("ontouchstart" in window)
{
  element.addEventListener("touchstart", doSomething);
}
else
{
  element.addEventListener("click", doSomething);
}

但是,此条件源自错误的假设 -由于设备支持触摸,因此将使用触摸。 但是,触摸屏笔记本电脑呢? 用户可能正在触摸屏幕,或者他们正在使用鼠标或触控板。 上面的代码无法解决这个问题,因此,用鼠标单击根本无法执行任何操作。

在这种情况下,解决方案是根本不测试事件的支持,而是立即绑定两个事件,然后使用preventDefault来阻止触摸产生点击:

element.addEventListener("touchstart", function(e)
{
  doSomething();
  
  e.preventDefault();
  	
}, false);
  
element.addEventListener("click", function()
{
  doSomething();
  
}, false);

普通的东西不起作用

要承认这是一件很痛苦的事情,但是有时它不是我们需要测试的功能- 它是浏览器 -因为特定的浏览器声称支持某些无效的功能。 最近的一个示例是Opera 12中的setDragImage() (这是拖放dataTransfer对象的方法 )。

功能测试在这里失败了,因为Opera 12声称支持它。 异常处理也无济于事,因为它不会引发任何错误。 只是不起作用:

//Opera 12 passes this condition, but the function does nothing
if("setDragImage" in e.dataTransfer)
{
  e.dataTransfer.setDragImage("ghost.png", -10, -10);
}

现在,如果您只想尝试添加自定义拖动图像,可能会很好,如果不支持该设置,则很乐意保留默认设置(这将会发生)。 但是,如果您的应用程序确实需要自定义图像,而在不支持该图像的浏览器中应提供完全不同的实现(即使用自定义JavaScript来实现所有拖动行为),该怎么办?

或者,如果浏览器实现了某些功能,但又呈现出无法避免的渲染错误,该怎么办? 有时,我们别无选择,只能明确检测出所涉及的浏览器 ,并将其排除在使用可能会尝试支持的功能之外。

因此,问题就变成了-实现浏览器检测的最安全方法是什么?

我有两个建议:

  1. 优先使用专有对象测试而不是navigator信息。
  2. 用它来排除浏览器而不是包括它们。

例如,可以使用window.opera对象检测Opera 12或更早版本,因此我们可以使用该排除条件测试可拖动的支持:

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")
{
}

但是,通常根据自动类型转换来编写测试条件:

if(foo.bar)
{
}

我们在某些浏览器对象测试(例如,针对window.opera的测试)中使用了较早的语法,这是安全的,因为对象是如何评估的-任何已定义的对象或函数都将始终评估为true ,而如果未定义,则始终为true将评估为false

但是,我们可能正在测试可以有效返回null或empty-string的东西,两者均评估为false 。 例如, style.maxWidth属性有时用于排除IE6

if(typeof document.documentElement.style.maxWidth != "undefined")
{
}

如果maxWidth属性受支持具有作者定义的值,则其评估结果为true ,因此,如果我们这样编写测试,则它可能会失败:

if(document.documentElement.style.maxWidth)
{
}

一般规则是这样的:依赖自动类型转换对对象和函数安全的 ,但对字符串和数字或可能为null的值不一定是安全的

话虽如此-如果您可以安全地使用它,那么请这样做,因为在现代浏览器中,它通常要快得多(大概是因为它们已针对这种情况进行了优化)。

有关此的更多信息,请参见: 现实世界中的自动类型转换

From: https://www.sitepoint.com/javascript-feature-detection-fails/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值