首页 > web前端 > js教程 > 当JavaScript功能检测失败时

当JavaScript功能检测失败时

尊渡假赌尊渡假赌尊渡假赌
发布: 2025-02-22 09:57:12
原创
807 人浏览过

When JavaScript Feature Detection Fails

关键要点

  • JavaScript 的特性检测(测试程序员想要使用的特性)并不总是可靠的。例如,在 Internet Explorer 中测试 ActiveXObject 以进行 Ajax 请求、映射到 DOM 属性的 HTML 属性以及对用户行为的假设(例如检测触摸设备)等。
  • 当特性检测失败时,有时需要采用浏览器检测。但是,建议使用专有对象测试而不是 navigator 信息,并将其用于排除浏览器而不是包含浏览器。
  • 在实现浏览器检测时,务必极其小心。始终首先假设完全符合特性测试,只有在知道某个特性无法按预期工作时才求助于浏览器检测。此外,用于对象和特性测试的语法会影响检测的成功率,因此选择正确的语法至关重要。

曾经,浏览器检测是 JavaScript 程序员的看家本领。如果我们知道某些功能在 IE5 中有效但在 Netscape 4 中无效,我们会测试该浏览器并相应地修改代码。例如:

if (navigator.userAgent.indexOf('MSIE 5') != -1) {
  // 我们认为此浏览器是 IE5
}
登录后复制
登录后复制
登录后复制
登录后复制

但是,当我第一次加入这个行业时,军备竞赛就已经开始了!供应商正在向用户代理字符串添加额外的值,因此它们看起来像是其竞争对手的浏览器,也是它们自己的浏览器。例如,这是 Mac 版 Safari 5:

<code>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</code>
登录后复制
登录后复制
登录后复制
登录后复制

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

添加所有这些值的目的是规避浏览器检测。如果脚本假设只有 Firefox 才能处理特定功能,否则可能会排除 Safari,即使它可能也能工作。别忘了用户自己可以更改他们的用户代理——我曾经将我的浏览器设置为识别为“Googlebot/1.0”,这样我就可以访问网站所有者认为仅供抓取的内容!

因此,随着时间的推移,这种浏览器检测已成为一个不可能解开的乱麻,并且在很大程度上已不再使用,取而代之的是更好的东西——特性检测。

特性检测只是测试我们想要使用的特性。例如,如果我们需要 getBoundingClientRect(获取元素相对于视口的位 置),那么重要的是浏览器是否支持它,而不是它是哪个浏览器;因此,与其测试受支持的浏览器,不如测试特性本身:

if (typeof document.documentElement.getBoundingClientRect != "undefined") {
  // 浏览器支持此函数
}
登录后复制
登录后复制
登录后复制
登录后复制

不支持该函数的浏览器将返回“undefined”类型,因此不会通过条件。无需在任何特定浏览器中测试脚本,我们就知道它要么正确工作,要么静默失败。

或者我们……?

但事实是——特性检测也不是完全可靠的——有时它会失败。因此,让我们现在看看一些示例,看看我们可以做些什么来解决每个案例。

ActiveX 对象

也许特性检测失败最著名的例子是测试 ActiveXObject 以在 Internet Explorer 中进行 Ajax 请求。

ActiveX 是后期绑定对象的示例,其实际意义是您无法知道它是否受支持直到您尝试使用它。因此,如果用户禁用了 ActiveX,则以下代码将引发错误:

if (navigator.userAgent.indexOf('MSIE 5') != -1) {
  // 我们认为此浏览器是 IE5
}
登录后复制
登录后复制
登录后复制
登录后复制

要解决此问题,我们需要使用异常处理——尝试实例化对象,捕获任何失败,并相应地处理它:

<code>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</code>
登录后复制
登录后复制
登录后复制
登录后复制

映射到 DOM 属性的 HTML 属性

属性映射通常用于测试与 HTML5 属性一起使用的 API 的支持。例如,通过查找可拖动属性来检查具有 [draggable="true"] 的元素是否支持拖放 API:

if (typeof document.documentElement.getBoundingClientRect != "undefined") {
  // 浏览器支持此函数
}
登录后复制
登录后复制
登录后复制
登录后复制

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

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

if (typeof window.ActiveXObject != "undefined") {
  var request = new ActiveXObject("Microsoft.XMLHTTP");
}
登录后复制
登录后复制
登录后复制

那么即使它们不支持,IE8 或更早版本也会返回 true 用于 ("draggable" in element)

属性可以是任何内容:

if (typeof window.ActiveXObject != "undefined") {
  try {
    var request = new ActiveXObject("Microsoft.XMLHTTP");
  } catch (ex) {
    request = null;
  }
  if (request !== null) {
    //... 我们有一个请求对象
  }
}
登录后复制
登录后复制
登录后复制

但结果将相同——IE8 或更早版本将返回 true 用于 ("nonsense" in element)

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

if ("draggable" in element) {
  // 浏览器支持拖放
}
登录后复制
登录后复制

对用户行为的假设

您可能已经看到使用以下代码来检测触摸设备:

<div draggable="true"> ... </div>
登录后复制

大多数触摸设备在触发点击事件之前会实现人工延迟(通常约为 300 毫秒),这是为了避免在双击元素的同时也点击它们。但这会使应用程序感觉迟缓且无响应,因此开发人员有时会使用该特性测试来分叉事件:

<div nonsense="true"> ... </div>
登录后复制

但是,此条件源于一个错误的假设——因为设备支持触摸,因此将使用触摸。但是触摸屏笔记本电脑呢?用户可能正在触摸屏幕,也可能正在使用鼠标或触控板;上面的代码无法处理这种情况,因此用鼠标单击将不会执行任何操作。

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

if (navigator.userAgent.indexOf('MSIE 5') != -1) {
  // 我们认为此浏览器是 IE5
}
登录后复制
登录后复制
登录后复制
登录后复制

完全不起作用的东西

承认这一点很痛苦,但有时我们不需要测试的不是特性——而是浏览器——因为特定浏览器声称支持某些不起作用的东西。最近的一个例子是 Opera 12 中的 setDragImage()(这是拖放 dataTransfer 对象的一种方法)。

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

<code>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</code>
登录后复制
登录后复制
登录后复制
登录后复制

现在,如果您只想尝试添加自定义拖动图像,并且乐于在不支持的情况下保留默认值(这将发生),那么这可能很好。但是,如果您的应用程序确实需要自定义图像,以至于不支持它的浏览器应该使用完全不同的实现(即使用自定义 JavaScript 来实现所有拖动行为)呢?

或者,如果浏览器实现了某些功能,但存在无法避免的渲染错误呢?有时我们别无选择,只能明确检测有问题的浏览器,并将其排除在使用它本来会尝试支持的功能之外。

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

我有两点建议:

  1. 优先使用专有对象测试而不是 navigator 信息。
  2. 将其用于排除浏览器而不是包含浏览器。

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

if (typeof document.documentElement.getBoundingClientRect != "undefined") {
  // 浏览器支持此函数
}
登录后复制
登录后复制
登录后复制
登录后复制

最好使用专有对象而不是标准对象,因为当发布新浏览器时,测试结果不太可能发生变化。以下是一些我最喜欢的示例:

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) {
    //... 我们有一个请求对象
  }
}
登录后复制
登录后复制
登录后复制

我们已经注意到用户代理字符串是一个不可靠的混乱,但供应商字符串实际上相当可预测,并且可以用来可靠地测试 Chrome 或 Safari:

if ("draggable" in element) {
  // 浏览器支持拖放
}
登录后复制
登录后复制

所有这一切的黄金法则是要极其小心。确保您在尽可能多的浏览器中测试条件,并仔细考虑它们的向前兼容性——目标是使用浏览器条件来排除浏览器,因为存在已知的错误,而不是因为已知的特性而包含它们(这就是特性测试的目的)

从根本上说,始终首先假设完全符合特性测试——除非您知道情况并非如此,否则假设特性将按预期工作。

选择测试语法

在结束之前,我想检查一下我们可以用于对象和特性测试的不同类型的语法。例如,近年来,以下语法已变得很常见:

if (navigator.userAgent.indexOf('MSIE 5') != -1) {
  // 我们认为此浏览器是 IE5
}
登录后复制
登录后复制
登录后复制
登录后复制

过去我们无法使用它,因为 IE5 及其同类产品会因语法而引发错误;但现在我们不必支持这些浏览器,这已不再是问题。

从本质上讲,它与以下内容完全相同,但编写起来更短:

<code>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</code>
登录后复制
登录后复制
登录后复制
登录后复制

但是,测试条件通常依赖于自动类型转换:

if (typeof document.documentElement.getBoundingClientRect != "undefined") {
  // 浏览器支持此函数
}
登录后复制
登录后复制
登录后复制
登录后复制

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

但是我们可能正在测试有效返回 null 或空字符串的东西,这两者都评估为 false。例如,style.maxWidth 属性有时用于排除 IE6:

if (typeof window.ActiveXObject != "undefined") {
  var request = new ActiveXObject("Microsoft.XMLHTTP");
}
登录后复制
登录后复制
登录后复制

只有在支持 maxWidth 属性并且具有作者定义的值时,它才会评估为 true,因此如果我们这样编写测试,它可能会失败:

if (typeof window.ActiveXObject != "undefined") {
  try {
    var request = new ActiveXObject("Microsoft.XMLHTTP");
  } catch (ex) {
    request = null;
  }
  if (request !== null) {
    //... 我们有一个请求对象
  }
}
登录后复制
登录后复制
登录后复制

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

话虽如此——如果您能安全地使用它,那就这样做,因为它在现代浏览器中通常要快得多(可能是因为它们针对这种类型的条件进行了优化)。

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

关于 JavaScript 特性检测的常见问题

什么是 JavaScript 特性检测,为什么它很重要?

JavaScript 特性检测是开发人员用来确定用户浏览器是否支持特定特性或 API 的一种技术。这至关重要,因为并非所有浏览器都支持 JavaScript 的所有特性。通过使用特性检测,开发人员可以为不受支持的特性提供替代解决方案或后备方案,确保网站或应用程序在不同浏览器上都能正确运行。这增强了用户体验并确保了兼容性。

JavaScript 特性检测是如何失败的?

JavaScript 特性检测可能会由于多种原因而失败。一个常见的原因是特性检测代码的实现不正确。例如,如果代码检查对象中不存在的属性,它将返回 undefined,导致假阴性。另一个原因可能是浏览器的怪癖或错误,这可能会导致特性检测给出不准确的结果。

特性检测和浏览器检测有什么区别?

特性检测涉及检查用户浏览器是否支持特定特性或 API,而浏览器检测则识别用户的浏览器和版本。虽然这两种技术都旨在确保兼容性和功能性,但特性检测通常被认为是一种更好的实践,因为它直接检查特性,而不是根据浏览器类型或版本来假设其支持。

如何使用 JavaScript 检测移动设备?

您可以使用 JavaScript 中的 navigator.userAgent 属性来检测移动设备。此属性返回一个字符串,表示浏览器的用户代理标头。通过检查此字符串中的特定关键字(例如“Android”、“iPhone”或“iPad”),您可以确定用户是否在移动设备上。

什么是 Feature.js,它如何帮助进行特性检测?

Feature.js 是一个轻量级、快速且简单的 JavaScript 实用程序,用于特性检测。它提供易于使用的 API,允许开发人员测试浏览器是否支持特定特性。这有助于为不受支持的特性提供后备方案或替代解决方案,从而增强网站或应用程序的兼容性和功能性。

什么是 Modernizr,它如何帮助进行特性检测?

Modernizr 是一个 JavaScript 库,可帮助开发人员利用 HTML5 和 CSS3 特性,同时保持与旧版浏览器的兼容性。它使用特性检测来检查浏览器是否支持特定特性,并将类添加到 HTML 元素,允许您在样式表或 JavaScript 中定位特定浏览器功能。

如何使用 device-detector-js 包进行特性检测?

device-detector-js 包是用于设备检测的强大工具。它解析用户代理字符串并检测智能手机、平板电脑、台式机、电视机等设备。它还检测浏览器、引擎、操作系统和其他有用信息。您可以使用此包根据检测到的设备调整网站或应用程序的行为。

实施特性检测的一些最佳实践是什么?

实施特性检测的一些最佳实践包括:使用可靠且经过测试的库(如 Modernizr 或 Feature.js)、在不同的浏览器和设备上彻底测试您的特性检测代码、为不受支持的特性提供替代解决方案或后备方案以及避免根据浏览器类型或版本来假设特性支持。

特性检测能否帮助提高网站性能?

是的,特性检测可以帮助提高网站性能。通过检测不受支持的特性并提供替代解决方案或后备方案,您可以防止不必要的代码在浏览器中运行。这可以减少加载时间并提高网站的整体性能。

如何了解不同浏览器支持的最新特性?

由于 Web 开发的快速发展,了解不同浏览器支持的最新特性可能具有挑战性。但是,Mozilla 开发者网络 (MDN)、Can I Use 和 JavaScript 文档等资源可以提供有关不同浏览器中特性支持的最新信息。

以上是当JavaScript功能检测失败时的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板