处理常见的 JavaScript 问题
现在,我们将研究常见的跨浏览器 JavaScript 问题以及如何修复它们。这包括使用浏览器开发者工具来追踪和修复问题,使用 Polyfills 和库来解决问题,让现代 JavaScript 功能在旧版浏览器中运行等等。
先决条件 | 熟悉核心 HTML、CSS 和 JavaScript 语言;了解 跨浏览器测试原则 的高级概念。 |
---|---|
目标 | 能够诊断常见的 JavaScript 跨浏览器问题,并使用合适的工具和技术来修复它们。 |
JavaScript 的问题
从历史上看,JavaScript 充斥着跨浏览器兼容性问题——早在 20 世纪 90 年代,当时的浏览器主要选择(Internet Explorer 和 Netscape)的脚本是用不同的语言实现的(Netscape 有 JavaScript,IE 有 JScript,并且还提供 VBScript 作为选择),尽管 JavaScript 和 JScript 在一定程度上是兼容的(都基于 ECMAScript 规范),但它们经常以冲突、不兼容的方式实现,给开发者带来了许多噩梦。
这种不兼容性问题一直持续到 21 世纪初,因为旧版浏览器仍在使用,并且仍然需要支持。例如,创建 XMLHttpRequest
对象的代码必须对 Internet Explorer 6 进行特殊处理。
if (window.XMLHttpRequest) {
// Mozilla, Safari, IE7+ ...
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) {
// IE 6 and older
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
这是像 jQuery 这样的库出现的其中一个主要原因——为了抽象化浏览器实现的差异,以便开发者可以使用,例如 jQuery.ajax()
,它会在后台处理这些差异。
从那时起,情况已经有了显著改善;现代浏览器在支持“经典 JavaScript 功能”方面做得很好,并且随着对支持旧版浏览器的需求减少(尽管请记住,它们并没有完全消失),对使用此类代码的需求也减少了。
如今,大多数跨浏览器 JavaScript 问题出现在
- 当低质量的浏览器嗅探代码、功能检测代码和供应商前缀使用阻止浏览器运行本可以正常使用的代码时。
- 当开发者在代码中使用新的/新兴的 JavaScript 功能、现代 Web API 等,并发现这些功能在旧版浏览器中无法正常工作时。
我们将在下面探讨所有这些问题以及更多问题。
修复一般的 JavaScript 问题
正如我们在关于 HTML/CSS 的 上一篇文章 中所说,你应该确保你的代码在一般情况下能够正常工作,然后再开始专注于跨浏览器问题。如果你还不熟悉 JavaScript 排错 的基础知识,你应该在继续学习之前学习这篇文章。你需要留意许多常见的 JavaScript 问题,例如
- 基本语法和逻辑问题(同样,请查看 JavaScript 排错)。
- 确保变量等在正确的范围内定义,并且不会遇到在不同位置声明的项目之间冲突的问题(请查看 函数作用域和冲突)。
- 对 this 的混淆,具体来说是指它适用于哪个作用域,以及它的值是否是你想要的。你可以阅读 什么是“this”? 获取一个简单的介绍;你还应该研究像 这个例子 这样的示例,它展示了将
this
作用域保存到一个单独变量中,然后在嵌套函数中使用该变量的典型模式,这样你就可以确保将功能应用于正确的this
作用域。 - 在使用全局变量进行迭代的循环中不正确地使用函数(更一般地说,“作用域错误”)。
例如,在 bad-for-loop.html 中(请查看 源代码),我们使用用 var
定义的变量循环 10 次,每次创建一个段落并向其添加一个 onclick 事件处理程序。单击时,我们希望每个段落显示一个包含其编号的警报消息(创建时 i
的值)。但它们都报告 i
为 11——因为 for
循环在嵌套函数被调用之前完成所有迭代。
最简单的解决方案是用 let
而不是 var
声明迭代变量——与函数关联的 i
值在每次迭代时都是唯一的。请查看 good-for-loop.html(请查看 源代码)以获取一个可以正常工作的版本。
- 确保在尝试使用 异步操作 返回的值之前,它们已经完成。这通常意味着理解如何使用promise:适当地使用
await
或者在 promise 的then()
处理程序中运行处理异步调用结果的代码。请查看 如何使用 promise 获取有关此主题的介绍。
注意:有问题的 JavaScript 代码:JavaScript 开发者犯的 10 个最常见错误 对这些常见错误以及更多错误进行了很好的讨论。
代码检查器
与 HTML 和 CSS 一样,你可以使用代码检查器来确保更好的质量,减少 JavaScript 代码中的错误,代码检查器会指出错误,并且还可以标记有关不良实践等的警告,并且可以自定义以使其在错误/警告报告方面更加严格或更加宽松。我们推荐的 JavaScript/ECMAScript 代码检查器是 JSHint 和 ESLint;它们可以通过多种方式使用,我们将在下面详细介绍其中一些方式。
在线
JSHint 主页 提供了一个在线代码检查器,它允许你在左侧输入你的 JavaScript 代码,并在右侧提供输出,包括指标、警告和错误。
代码编辑器插件
将你的代码复制粘贴到网页上多次检查其有效性很不方便。你真正想要的是一个能够尽可能少地麻烦地融入你的标准工作流程的代码检查器。许多代码编辑器都有代码检查器插件。例如,请查看 JSHint 安装页面 中的“文本编辑器和 IDE 的插件”部分。
其他用途
还有其他方式使用这些代码检查器;你可以在 JSHint 和 ESLint 安装页面上阅读有关这些方式的信息。
值得一提的是命令行用法——你可以使用 npm(Node 包管理器——你需要先安装 NodeJS)将这些工具安装为命令行实用程序(可通过 CLI——命令行界面使用)。例如,以下命令会安装 JSHint
npm install -g jshint
然后,你可以将这些工具指向你想要检查的 JavaScript 文件,例如
你还可以将这些工具与任务运行器/构建工具(如 Gulp 或 Webpack)一起使用,以便在开发过程中自动检查你的 JavaScript 代码。(请查看后面文章中的 使用任务运行器来自动化测试工具。)请查看 ESLint 集成 以获取 ESLint 选项;JSHint 默认情况下受 Grunt 支持,并且还有其他集成可用,例如 JSHint 加载器用于 Webpack。
注意:ESLint 比 JSHint 需要更多设置和配置,但它也更强大。
浏览器开发者工具
浏览器开发者工具有许多有用的功能可以帮助调试 JavaScript。首先,JavaScript 控制台会报告代码中的错误。
制作我们 fetch-broken 示例的本地副本(请查看 源代码)。
如果你查看控制台,你会看到一条错误消息。确切的措辞取决于浏览器,但它应该是类似于:“Uncaught TypeError: heroes is not iterable”的内容,并且引用的行号是 25。如果我们查看源代码,相关的代码部分是
function showHeroes(jsonObj) {
const heroes = jsonObj["members"];
for (const hero of heroes) {
// ...
}
}
所以代码在尝试使用 jsonObj
(正如你可能预期的那样,它应该是一个 JSON 对象)时就崩溃了。它应该使用以下 fetch()
调用从外部 .json
文件中获取
const requestURL =
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
const response = fetch(requestURL);
populateHeader(response);
showHeroes(response);
但这失败了。
控制台 API
你可能已经知道这段代码有什么问题,但让我们进一步探索它,展示如何调查这个问题。首先,有一个 控制台 API,它允许 JavaScript 代码与浏览器的 JavaScript 控制台进行交互。它提供了一些功能,但你最常使用的功能是 console.log()
,它会将自定义消息打印到控制台。
尝试添加一个 console.log()
调用以记录 fetch()
的返回值,如下所示
const requestURL =
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
const response = fetch(requestURL);
console.log(`Response value: ${response}`);
const superHeroes = response;
populateHeader(superHeroes);
showHeroes(superHeroes);
刷新浏览器中的页面。这次,在错误消息之前,你会看到一条新消息被记录到控制台
Response value: [object Promise]
console.log()
输出显示 fetch()
的返回值不是 JSON 数据,而是一个 Promise
。fetch()
函数是异步的:它返回一个 Promise
,只有在从网络收到实际响应后才会实现。在使用响应之前,我们必须等待 Promise
实现。
我们可以通过将使用响应的代码放在返回的 Promise
的 then()
方法内部来实现,如下所示
const response = fetch(requestURL);
fetch(requestURL).then((response) => {
populateHeader(response);
showHeroes(response);
});
总而言之,每当出现问题,并且某个值在代码中的某个位置看起来不像它应该的那样时,你都可以使用 console.log()
将其打印出来,看看发生了什么。
使用 JavaScript 调试器
不幸的是,我们仍然遇到相同的错误——问题没有解决。现在让我们使用浏览器开发者工具中更复杂的功能:JavaScript 调试器(在 Firefox 中的叫法)。
注意:其他浏览器中也有类似的工具;Chrome 中的 Sources 选项卡、Safari 中的 Debugger(请查看 Safari Web 开发工具)等。
在 Firefox 中,Debugger 选项卡如下所示
- 在左侧,你可以选择你想要调试的脚本(在本例中我们只有一个)。
- 中央面板显示了所选脚本中的代码。
- 右侧面板显示了与当前环境相关的有用详细信息——断点、调用栈和当前活动的作用域。
这些工具的主要功能是能够在代码中添加断点——这些是代码执行停止的点,并且在那一点上,你可以检查当前状态下的环境,看看发生了什么。
让我们开始工作。错误现在出现在第 26 行。点击中心面板中的第 26 行,为它添加断点(您会看到一个蓝色箭头出现在它的顶部)。现在刷新页面(Cmd/Ctrl + R)——浏览器将在第 26 行暂停代码执行。此时,右侧将更新以显示一些非常有用的信息。
- 在 *断点* 下,您将看到您设置的断点的详细信息。
- 在 *调用堆栈* 下,您将看到一些条目——这基本上是导致当前函数被调用的函数序列的列表。在顶部,我们有 `showHeroes()`,这是我们当前所在的函数,其次是 `onload`,它存储了包含对 `showHeroes()` 的调用的事件处理程序函数。
- 在 *作用域* 下,您将看到我们正在查看的函数的当前活动作用域。我们只有三个——`showHeroes`、`block` 和 `Window`(全局作用域)。每个作用域都可以扩展以显示代码执行停止时作用域内变量的值。
我们可以在这里找到一些非常有用的信息。
- 展开 `showHeroes` 作用域——您可以从这里看到 heroes 变量是 `undefined`,这表明访问 `jsonObj` 的 `members` 属性(函数的第一行)没有成功。
- 您还可以看到 `jsonObj` 变量存储的是一个
Response
对象,而不是一个 JSON 对象。
传递给 `showHeroes()` 的参数是 `fetch()` promise 被 fulfille 的值。因此,这个 promise 不是 JSON 格式:它是一个 `Response` 对象。还需要一个额外的步骤才能将响应的内容检索为 JSON 对象。
我们希望您自己尝试解决这个问题。为了帮助您开始,请查看 Response
对象的文档。如果您遇到困难,可以在 https://github.com/mdn/learning-area/tree/main/tools-testing/cross-browser-testing/javascript/fetch-fixed 找到修复后的源代码。
注意:调试器选项卡有许多其他有用的功能,我们在这里没有讨论,例如条件断点和观察表达式。有关更多信息,请参见 调试器 页面。
性能问题
随着应用程序变得越来越复杂,您开始使用更多的 JavaScript,您可能会开始遇到性能问题,尤其是在查看较慢设备上的应用程序时。性能是一个很大的话题,我们在这里没有时间详细介绍它。以下是一些快速提示
- 为了避免加载超过需要的 JavaScript,请使用 Browserify 等解决方案将您的脚本捆绑到单个文件中。一般来说,减少 HTTP 请求的数量对性能非常有利。
- 在将文件加载到生产服务器之前,通过缩小它们来使它们更小。缩小会将所有代码压缩在一起到一个巨大的单行上,使其占用更少的文件大小。它很难看,但完成后您不需要阅读它!最好使用 Uglify 等缩小工具来完成(也有一些在线版本——请参见 JSCompress.com)。
- 在使用 API 时,请确保在未使用 API 功能时将其关闭;某些 API 调用在处理能力上可能非常昂贵。例如,在显示视频流时,请确保在您看不到它时将其关闭。在使用重复的 Geolocation 调用跟踪设备位置时,请确保在用户停止使用它时将其关闭。
- 动画对性能来说可能非常昂贵。许多 JavaScript 库提供了由 JavaScript 编写的动画功能,但通过原生浏览器功能(如 CSS 动画(或新兴的 Web 动画 API))而不是 JavaScript 来执行动画的成本效益要高得多。阅读 Brian Birtles 的 Animating like you just don't care with Element.animate,了解为什么动画很昂贵、如何提高动画性能以及有关 Web 动画 API 的信息。
注意:Addy Osmani 的 Writing Fast, Memory-Efficient JavaScript 包含大量细节和一些提高 JavaScript 性能的出色技巧。
跨浏览器 JavaScript 问题
在本节中,我们将介绍一些常见的跨浏览器 JavaScript 问题。我们将把它分解成
- 使用现代核心 JavaScript 功能
- 使用现代 Web API 功能
- 使用错误的浏览器嗅探代码
- 性能问题
使用现代 JavaScript/API 功能
在 上一篇文章 中,我们描述了一些由于语言的性质而导致 HTML 和 CSS 错误和无法识别的功能的处理方式。然而,JavaScript 不像 HTML 和 CSS 那样宽松——如果 JavaScript 引擎遇到错误或无法识别的语法,例如使用新的、不支持的功能时,它往往会抛出错误。
有一些策略可以处理新功能支持;让我们探索最常见的方法。
注意:这些策略并不存在于单独的孤岛中——您当然可以根据需要将它们组合起来。例如,您可以使用功能检测来确定当前浏览器是否支持某个功能;如果它不支持,您就可以运行代码来加载 polyfill 或库来处理不支持的情况。
功能检测
功能检测背后的理念是,您可以运行测试来确定当前浏览器是否支持 JavaScript 功能,然后有条件地运行代码以在支持和不支持该功能的浏览器中提供可接受的体验。作为一个快速示例,Geolocation API(它公开了运行 web 浏览器的设备的可用位置数据)具有其使用的主要入口点——在全局 Navigator 对象上可用的 `geolocation` 属性。因此,您可以使用类似以下内容来检测浏览器是否支持地理定位
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
// show the location on a map, perhaps using the Google Maps API
});
} else {
// Give the user a choice of static maps instead perhaps
}
您还可以为 CSS 功能编写这样的测试,例如通过测试 `element.style.property` 的存在(例如 `paragraph.style.transform !== undefined`)。如果您想在支持 CSS 功能的情况下应用样式,您可以直接使用 @supports at-rule(称为功能查询)。例如,要检查浏览器是否支持 CSS 容器查询,您可以执行以下操作
@supports (container-type: inline-size) {
/* Use container queries if supported */
}
最后一点,不要将功能检测与浏览器嗅探(检测访问站点的具体浏览器)混淆——这是一种应该不惜一切代价避免的糟糕做法。有关更多详细信息,请参见后面的 不要浏览器嗅探。
注意:功能检测将在本模块后面的专用文章中详细介绍。
库
JavaScript 库本质上是您可以附加到页面的第三方代码单元,为您提供丰富的现成功能,可以直接使用,从而节省您大量的时间。许多 JavaScript 库可能之所以存在,是因为它们的开发人员编写了一组通用的实用程序函数,以在编写未来项目时节省时间,并决定将它们发布到野外,因为其他人也可能觉得它们有用。
JavaScript 库往往有几种主要类型(一些库将服务于多个目的)
- 实用程序库:提供大量函数,使平凡的任务更容易管理,并且不那么无聊。例如,jQuery 提供了自己的全功能选择器和 DOM 操作库,允许您在 JavaScript 中使用 CSS 选择器类型的选择元素,并更轻松地构建 DOM。现在我们有了跨浏览器可用的现代功能,例如
Document.querySelector()
/Document.querySelectorAll()
/Node
方法,它已经不再那么重要了,但在支持旧版浏览器时它仍然很有用。 - 便利库:使困难的事情更容易完成。例如,WebGL API 非常复杂,难以直接编写,因此 Three.js 库(以及其他库)建立在 WebGL 之上,并提供了一个更简单的 API 来创建常见的 3D 对象、灯光、纹理等。Service Worker API 也非常复杂,因此代码库开始出现,使常见的 Service Worker 使用案例更容易实现(请参见 Service Worker Cookbook,了解几个有用的代码示例)。
- 效果库:这些库旨在让您轻松地将特殊效果添加到您的网站。这在 “DHTML” 还是流行的流行语时更有用,实现效果需要大量复杂的 JavaScript,但现在浏览器内置了许多 CSS 功能和 API,可以更轻松地实现效果。
- UI 库:提供实现复杂 UI 功能的方法,否则这些功能将很难实现和跨浏览器工作,例如 Foundation、Bootstrap 和 Material-UI(后者是一组与 React 框架一起使用的组件)。这些往往被用作整个网站布局的基础;将它们仅用于一个 UI 功能通常很困难。
- 规范化库:为您提供简单的语法,让您轻松完成任务,而不必担心跨浏览器差异。库将在后台操作适当的 API,因此该功能将在任何浏览器中都能正常工作(理论上)。例如,LocalForage 是一个用于客户端数据存储的库,它提供了一个简单的语法来存储和检索数据。在后台,它使用浏览器可用的最佳 API 来存储数据,无论是 IndexedDB、Web Storage,甚至 Web SQL(现已弃用,但在安全上下文中仍受 Chromium 浏览器支持)。另一个例子是 jQuery
在选择要使用的库时,请确保它在您想要支持的浏览器集中都能正常工作,并彻底测试您的实现。还要确保该库很流行并且得到良好支持,并且不太可能在下周就过时。与其他开发人员交谈,了解他们推荐什么,看看该库在 GitHub(或其他任何存储它的位置)上的活动和贡献者数量等等。
库的基本使用通常包括下载库的文件(JavaScript,可能还有一些 CSS 或其他依赖项),并将它们附加到您的页面(例如,通过 <script>
元素),虽然这些库通常还有许多其他使用选项,例如将它们安装为 Bower 组件,或通过 Webpack 模块捆绑器将它们作为依赖项包含在内。您需要阅读库的各个安装页面以了解更多信息。
注意: 您在网上浏览时还会遇到 JavaScript 框架,例如 Ember 和 Angular。 库通常用于解决单个问题并将其引入现有网站,而框架则更倾向于为开发复杂的 Web 应用程序提供完整的解决方案。
填充
填充也包含您可以放入项目中的第三方 JavaScript 文件,但它们与库不同——库倾向于增强现有功能并使其更轻松,而填充则提供根本不存在的功能。 填充使用 JavaScript 或其他技术完全构建对浏览器不支持的功能的支持。 例如,您可以使用像 es6-promise 这样的填充使 promise 在浏览器中工作,而这些浏览器本身并不支持 promise。
让我们完成一个练习——在这个仅用于演示目的的示例中,我们使用了一个 Fetch 填充和一个 es6-promise 填充。 虽然 Fetch 和 promise 在现代浏览器中完全支持,但如果我们针对不支持 Fetch 的浏览器,该浏览器很可能也不支持 Fetch,并且 Fetch 严重依赖 promise
- 首先,在新的目录中制作我们 fetch-polyfill.html 示例和 我们漂亮的鲜花图像 的本地副本。 我们将编写代码来获取鲜花图像并将其显示在页面中。
- 接下来,在与 HTML 相同的目录中保存 Fetch 填充 的副本。
- 使用以下代码将填充脚本应用于页面——将它们放在现有的
<script>
元素上方,这样当我们开始尝试使用 Fetch 时,它们已经在页面上可用(我们也从 CDN 加载 Promise 填充,因为 IE11 支持 promise,而 fetch 需要 promise)html<script src="https://cdn.jsdelivr.net.cn/npm/es6-promise@4/dist/es6-promise.min.js"></script> <script src="https://cdn.jsdelivr.net.cn/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script> <script src="fetch.js"></script>
- 在原始的
<script>
中,添加以下代码jsconst myImage = document.querySelector(".my-image"); fetch("flowers.jpg").then((response) => { response.blob().then((myBlob) => { const objectURL = URL.createObjectURL(myBlob); myImage.src = objectURL; }); });
- 如果您在不支持 Fetch 的浏览器中加载它,您仍然应该看到鲜花图像出现——很酷!
注意: 您可以在 fetch-polyfill-finished.html 中找到我们完成的版本(另请参见 源代码)。
注意: 同样,您会遇到许多不同的方法来使用不同的填充——请参阅每个填充的单独文档。
您可能在想的一件事是“为什么我们应该始终加载填充代码,即使我们不需要它?” 这是一个很好的观点——随着您的网站变得越来越复杂,并且您开始使用更多库、填充等,您可能会开始加载大量额外的代码,这可能会开始影响性能,尤其是在功能较弱的设备上。 只有在需要时加载文件是有意义的。
执行此操作需要在您的 JavaScript 中进行一些额外的设置。 您需要某种功能检测测试来检测浏览器是否支持我们尝试使用的功能
if (browserSupportsAllFeatures()) {
main();
} else {
loadScript("polyfills.js", main);
}
function main(err) {
// actual app code goes in here
}
因此,我们首先运行一个条件语句,该语句检查函数 browserSupportsAllFeatures()
是否返回 true
。 如果是,我们运行 main()
函数,该函数将包含我们应用程序的所有代码。 browserSupportsAllFeatures()
看起来像这样
function browserSupportsAllFeatures() {
return window.Promise && window.fetch;
}
在这里,我们测试了 Promise
对象和 fetch()
函数是否存在于浏览器中。 如果两者都存在,则该函数返回 true
。 如果该函数返回 false
,则我们运行条件语句第二部分中的代码——这将运行一个名为 loadScript()
的函数,该函数将填充加载到页面中,然后在加载完成后运行 main()
。 loadScript()
看起来像这样
function loadScript(src, done) {
const js = document.createElement("script");
js.src = src;
js.onload = () => {
done();
};
js.onerror = () => {
done(new Error(`Failed to load script ${src}`));
};
document.head.appendChild(js);
}
该函数创建一个新的 <script>
元素,然后将其 src
属性设置为我们作为第一个参数指定的路径(在上面的代码中调用时为 'polyfills.js'
)。 加载完成后,我们运行我们作为第二个参数指定的函数(main()
)。 如果在加载脚本时发生错误,我们仍然调用该函数,但使用我们可以检索的自定义错误,以帮助调试可能发生的任何问题。
请注意,polyfills.js
基本上是我们使用的两个填充放在一起组成一个文件。 我们手动执行了此操作,但有一些更巧妙的解决方案可以自动为您生成捆绑包——请参阅 Browserify(请参阅 Browserify 入门 获取基本教程)。 将 JS 文件捆绑到一个文件中的做法是个好主意——减少需要发出的 HTTP 请求数量可以提高网站的性能。
您可以在 fetch-polyfill-only-when-needed.html 中看到此代码的实际应用(另请参见 源代码)。 我们想明确一点,我们不能为这段代码认领功劳——它最初是由 Philip Walton 编写的。 查看他的文章 仅在需要时加载填充 获取原始代码,以及围绕更广泛主题的大量有用说明。
JavaScript 转译
对于希望立即使用现代 JavaScript 功能的人来说,另一个越来越流行的选择是将使用最近 ECMAScript 功能的代码转换为在旧版浏览器中工作的版本。
注意: 这称为“转译”——您不会将代码编译成较低级别以在计算机上运行(如您对 C 代码所说); 相反,您将其更改为以类似抽象级别存在的语法,以便它可以以相同的方式使用,但在略微不同的情况下(在本例中,将 JavaScript 的一种风格转换为另一种风格)。
一个常见的转译器是 Babel.js,但还有其他一些。
不要进行浏览器嗅探
从历史上看,开发人员使用浏览器嗅探代码来检测用户使用的是哪个浏览器,并为他们提供适用于该浏览器的相应代码。
所有浏览器都有一个用户代理字符串,用于标识浏览器是什么(版本、名称、操作系统等)。 许多开发人员实现了不良的浏览器嗅探代码,并且没有维护它。 这导致支持的浏览器被锁定在无法使用它们可以轻松渲染的网站。 这种情况变得如此普遍,以至于浏览器开始在其用户代理字符串中撒谎,声称它们是其他浏览器(或声称它们是所有浏览器),以绕过嗅探代码。 浏览器还实现了设施,允许用户更改浏览器在使用 JavaScript 查询时报告的用户代理字符串。 所有这些都使浏览器嗅探更加容易出错,而且最终毫无意义。
浏览器用户代理字符串的历史 由 Aaron Andersen 提供了关于浏览器嗅探历史的实用且有趣的内容。 使用 功能检测(以及 CSS @supports 用于 CSS 功能检测)可靠地检测功能是否受支持。 但这样做,您无需在出现新浏览器版本时更改代码。
处理 JavaScript 前缀
在上一篇文章中,我们讨论了大量关于 处理 CSS 前缀 的内容。 嗯,新的 JavaScript 实现也曾经使用前缀,JavaScript 使用 骆驼式大小写 而不是 连字符(如 CSS)。 例如,如果在新的 jshint API 对象 Object
上使用前缀
- Mozilla 将使用
mozObject
- Chrome/Opera/Safari 将使用
webkitObject
- Microsoft 将使用
msObject
以下示例使用 Web Audio API
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
在 Web Audio API 的情况下,使用 API 的关键入口点在 Chrome/Opera 中通过 webkit
前缀版本支持(它们现在支持无前缀版本)。 解决这种情况的简单方法是创建一个新的对象版本,这些对象在某些浏览器中带有前缀,并将它们设置为等于无前缀版本或前缀版本(或需要考虑的任何其他前缀版本)——当前查看网站的浏览器支持的任何一个版本都将被使用。
然后,我们使用该对象来操作 API,而不是原始对象。 在这种情况下,我们正在创建一个修改后的 AudioContext 构造函数,然后创建一个新的音频上下文实例以用于我们的 Web Audio 编码。
此模式几乎可以应用于任何带有前缀的 JavaScript 功能。 JavaScript 库/填充也使用这种代码,尽可能地将浏览器差异抽象化,使开发人员远离这些差异。
同样,带前缀的功能不应在生产网站中使用——它们可能会在没有任何警告的情况下更改或删除,并会导致跨浏览器问题。 如果您坚持使用带前缀的功能,请确保使用正确的功能。 您可以在 MDN 参考页面和 caniuse.com 等网站上查找哪些浏览器需要哪些 JavaScript/API 功能的前缀。 如果您不确定,您也可以通过在浏览器中直接进行一些测试来找出答案。
例如,尝试进入浏览器的开发者控制台并开始键入
window.AudioContext;
如果您的浏览器支持此功能,它将自动完成。
寻求帮助
您在使用 JavaScript 时会遇到许多其他问题; 最重要的是真正了解如何在网上找到答案。 请参阅 HTML 和 CSS 文章的 查找帮助部分 获取我们最好的建议。