当JavaScript功能检测失败时
关键要点
- 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 来实现所有拖动行为)呢?
或者,如果浏览器实现了某些功能,但存在无法避免的渲染错误呢?有时我们别无选择,只能明确检测有问题的浏览器,并将其排除在使用它本来会尝试支持的功能之外。
因此,问题变成了——实现浏览器检测最安全的方法是什么?
我有两点建议:
- 优先使用专有对象测试而不是 navigator 信息。
- 将其用于排除浏览器而不是包含浏览器。
例如,可以使用 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中文网其他相关文章!

热AI工具

Undress AI Tool
免费脱衣服图片

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

PlacingtagsatthebottomofablogpostorwebpageservespracticalpurposesforSEO,userexperience,anddesign.1.IthelpswithSEObyallowingsearchenginestoaccesskeyword-relevanttagswithoutclutteringthemaincontent.2.Itimprovesuserexperiencebykeepingthefocusonthearticl

事件捕获和冒泡是DOM中事件传播的两个阶段,捕获是从顶层向下到目标元素,冒泡是从目标元素向上传播到顶层。1.事件捕获通过addEventListener的useCapture参数设为true实现;2.事件冒泡是默认行为,useCapture设为false或省略;3.可使用event.stopPropagation()阻止事件传播;4.冒泡支持事件委托,提高动态内容处理效率;5.捕获可用于提前拦截事件,如日志记录或错误处理。了解这两个阶段有助于精确控制JavaScript响应用户操作的时机和方式。

ES模块和CommonJS的主要区别在于加载方式和使用场景。1.CommonJS是同步加载,适用于Node.js服务器端环境;2.ES模块是异步加载,适用于浏览器等网络环境;3.语法上,ES模块使用import/export,且必须位于顶层作用域,而CommonJS使用require/module.exports,可在运行时动态调用;4.CommonJS广泛用于旧版Node.js及依赖它的库如Express,ES模块则适用于现代前端框架和Node.jsv14 ;5.虽然可混合使用,但容易引发问题

JavaScript的垃圾回收机制通过标记-清除算法自动管理内存,以减少内存泄漏风险。引擎从根对象出发遍历并标记活跃对象,未被标记的则被视为垃圾并被清除。例如,当对象不再被引用(如将变量设为null),它将在下一轮回收中被释放。常见的内存泄漏原因包括:①未清除的定时器或事件监听器;②闭包中对外部变量的引用;③全局变量持续持有大量数据。V8引擎通过分代回收、增量标记、并行/并发回收等策略优化回收效率,降低主线程阻塞时间。开发时应避免不必要的全局引用、及时解除对象关联,以提升性能与稳定性。

在Node.js中发起HTTP请求有三种常用方式:使用内置模块、axios和node-fetch。1.使用内置的http/https模块无需依赖,适合基础场景,但需手动处理数据拼接和错误监听,例如用https.get()获取数据或通过.write()发送POST请求;2.axios是基于Promise的第三方库,语法简洁且功能强大,支持async/await、自动JSON转换、拦截器等,推荐用于简化异步请求操作;3.node-fetch提供类似浏览器fetch的风格,基于Promise且语法简单

var、let和const的区别在于作用域、提升和重复声明。1.var是函数作用域,存在变量提升,允许重复声明;2.let是块级作用域,存在暂时性死区,不允许重复声明;3.const也是块级作用域,必须立即赋值,不可重新赋值,但可修改引用类型的内部值。优先使用const,需改变变量时用let,避免使用var。

JavaScript的数据类型分为原始类型和引用类型。原始类型包括string、number、boolean、null、undefined和symbol,其值不可变且赋值时复制副本,因此互不影响;引用类型如对象、数组和函数存储的是内存地址,指向同一对象的变量会相互影响。判断类型可用typeof和instanceof,但需注意typeofnull的历史问题。理解这两类差异有助于编写更稳定可靠的代码。

DOM遍历是网页元素操作的基础,常用方法包括:1.使用parentNode获取父节点,可链式调用向上查找;2.children返回子元素集合,通过索引访问首个或末尾子元素;3.nextElementSibling获取下一个兄弟元素,结合previousElementSibling实现同级导航。实际应用如动态修改结构、交互效果等,例如点击按钮高亮下一个兄弟节点,掌握这些方法后复杂操作可通过组合实现。
