首页 > web前端 > js教程 > 如何避免通过Localstorage和其他罪魁祸首阻止DOM

如何避免通过Localstorage和其他罪魁祸首阻止DOM

Jennifer Aniston
发布: 2025-02-14 09:13:12
原创
636 人浏览过

How to Avoid DOM Blocking by localStorage and Other Culprits

关键要点

  • 同步 JavaScript 操作(例如计算、DOM 更新、使用 localStorage 或 IndexedDB 存储和检索数据)会阻塞 DOM 更新并影响前端性能。
  • Web Workers 可用于处理长时间运行的进程。它们允许主浏览器应用程序启动后台脚本并使用消息事件进行通信,但它们无法直接访问 DOM 或 localStorage。
  • 硬件加速的 CSS 动画在自己的图层中运行,可以提高动画的流畅度,并且不会被大多数现代浏览器阻塞。
  • 内存存储比基于磁盘的存储机制提供更快的数据更新。建议使用内存对象以提高性能,并在方便的时候永久存储数据。

浏览器和 Node.js 等运行环境中的 JavaScript 程序在单线程上运行。当代码在浏览器标签页中执行时,其他所有操作都会停止:菜单命令、下载、渲染、DOM 更新甚至 GIF 动画。

用户很少会注意到这一点,因为处理过程以小的块快速进行。例如:单击一个按钮,引发一个事件,运行一个函数,进行计算并更新 DOM。完成后,浏览器可以自由处理处理队列中的下一个项目。

JavaScript 代码无法等待某些事情发生;想象一下,如果应用程序每次发出 Ajax 请求时都冻结,那该有多么令人沮丧。因此,JavaScript 代码使用事件和回调来操作:指示浏览器或操作系统级别的进程在操作完成后且结果准备就绪时调用特定函数。

在以下示例中,当按钮单击事件发生时,会执行一个处理程序函数,该函数通过应用 CSS 类来设置动画。当动画完成时,匿名回调将删除该类:

// 单击按钮时引发事件
document.getElementById('clickme').addEventListener('click', handleClick);

// 处理按钮单击事件
function handleClick(e) {

  // 获取要设置动画的元素
  let sprite = document.getElementById('sprite');
  if (!sprite) return;

  // 动画结束时删除“animate”类
  sprite.addEventListener('animationend', () => {
    sprite.classList.remove('animate');
  });

  // 添加“animate”类
  sprite.classList.add('animate');
}
登录后复制
登录后复制

ES2015 提供了 Promise,ES2017 引入了 async/await 以简化编码,但在表面之下仍然使用回调。有关更多信息,请参阅“现代 JS 中的流程控制”。

阻塞因素

不幸的是,某些 JavaScript 操作将始终是同步的,包括:

  • 运行计算
  • 更新 DOM
  • 使用 localStorage 或 IndexedDB 存储和检索数据。

以下示例展示了一个入侵者,它结合使用 CSS 动画进行移动和 JavaScript 来挥动肢体。右侧的图像是基本的动画 GIF。使用默认的 100,000 个 sessionStorage 操作点击“写入”按钮:

[CodePen 示例链接 - 此处应插入CodePen的嵌入代码]

在此操作期间,DOM 更新被阻止。在大多数浏览器中,入侵者会停止或卡顿。某些动画 GIF 动画将暂停。较慢的设备可能会显示“脚本无响应”警告。

这是一个复杂的示例,但它演示了基本操作如何影响前端性能。

Web Workers

长时间运行进程的一种解决方案是 Web Workers。这些允许主浏览器应用程序启动后台脚本并使用消息事件进行通信。例如:

// 单击按钮时引发事件
document.getElementById('clickme').addEventListener('click', handleClick);

// 处理按钮单击事件
function handleClick(e) {

  // 获取要设置动画的元素
  let sprite = document.getElementById('sprite');
  if (!sprite) return;

  // 动画结束时删除“animate”类
  sprite.addEventListener('animationend', () => {
    sprite.classList.remove('animate');
  });

  // 添加“animate”类
  sprite.classList.add('animate');
}
登录后复制
登录后复制

Web Worker 脚本:

// main.js
// 是否支持 Web Workers?
if (!window.Worker) return;

// 启动 Web Worker 脚本
let myWorker = new Worker('myworker.js');

// 从 myWorker 接收消息
myWorker.onmessage = e => {
  console.log('myworker sent:', e.data);
}

// 向 myWorker 发送消息
myWorker.postMessage('hello');
登录后复制

一个 worker 甚至可以生成其他 worker 来模拟复杂的线程式操作。但是,worker 的功能受到有意限制,并且 worker 不能直接访问 DOM 或 localStorage(这样做实际上会使 JavaScript 多线程化并破坏浏览器的稳定性)。因此,所有消息都作为字符串发送,这允许传递 JSON 编码的对象,但不允许传递 DOM 节点。

Workers 可以访问某些窗口属性、WebSockets 和 IndexDB,但它们不会改进上面显示的示例。在大多数情况下,worker 用于长时间运行的计算——例如光线追踪、图像处理、比特币挖掘等等。

(Node.js 提供子进程,它们类似于 Web Workers,但可以选择运行其他语言编写的可执行文件。)

硬件加速动画

大多数现代浏览器不会阻塞在自己的图层中运行的硬件加速 CSS 动画。

默认情况下,上面的示例通过更改左外边距来移动入侵者。此属性和类似的属性(例如 left 和 width)会导致浏览器在每个动画步骤中重新流式传输和重新绘制整个文档。

使用 transform 和/或 opacity 属性时,动画效率更高。这些实际上将元素放入单独的合成图层中,以便 GPU 可以单独设置其动画。

单击“硬件加速”复选框,动画将立即变得更流畅。现在尝试另一个 sessionStorage 写入;即使动画 GIF 停止,入侵者也会继续移动。请注意,肢体运动仍然会暂停,因为这是由 JavaScript 控制的。

内存存储

在内存中更新对象的速度比使用写入磁盘的存储机制要快得多。在上面的示例中选择“对象”存储类型并点击“写入”。结果会有所不同,但它应该比等效的 sessionStorage 操作快大约 10 倍。

内存是易失性的:关闭标签页或导航离开会导致所有数据丢失。一个很好的折衷方案是使用内存对象来提高性能,然后在方便的时候永久存储数据——例如在页面卸载时:

// myworker.js
// 接收消息时启动
onmessage = e => {
  console.log('myworker received:', e.data);
  // ...长时间运行的进程...
  // 发送回消息
  postMessage('result');
};
登录后复制

游戏或单页应用程序可能需要更复杂的选择。例如,保存数据的时间点:

  • 在一段时间内没有用户活动(鼠标、触摸或键盘事件)
  • 游戏暂停或应用程序标签在后台(参见页面可见性 API)
  • 有自然的暂停——例如玩家死亡、完成关卡、在主屏幕之间移动等等。

Web 性能

Web 性能是一个热门话题。开发人员受浏览器限制的约束较小,用户期望获得快速、类似操作系统的应用程序性能。

尽可能少地进行处理,DOM 将永远不会被明显阻塞。幸运的是,在无法避免长时间运行的任务的情况下,有一些选择。

用户和客户端可能永远不会注意到您的速度优化,但当应用程序变慢时,他们总是会抱怨!

关于 DOM 阻塞的常见问题解答 (FAQ)

什么是 DOM 阻塞以及为什么它是一个问题?

DOM 阻塞是指浏览器无法渲染网页,因为它正在等待脚本完成加载。这会大大降低网页的加载速度,从而导致用户体验不佳。浏览器必须通过解析 HTML 标记来构建 DOM 树。在此过程中,如果遇到脚本,则必须停止并执行它才能继续。这是因为脚本可能会更改 DOM 树结构,并且浏览器需要确保它拥有最新的视图。

如何避免 DOM 阻塞?

有几种方法可以避免 DOM 阻塞。最有效的方法之一是使用异步加载脚本。这意味着脚本将在后台加载,而页面其余部分将继续加载。另一种方法是推迟脚本,这意味着它们只会在 HTML 文档完全解析后才执行。最后,您还可以将脚本移到 HTML 文档的底部,以便它们是最后加载的内容。

同步脚本和异步脚本有什么区别?

同步脚本会阻塞 DOM 构造,直到它们完全加载并执行。这意味着如果脚本加载时间很长,它会延迟整个网页。另一方面,异步脚本不会阻塞 DOM 构造。它们在后台加载,并且一旦准备好就可以执行,即使 DOM 尚未完全构建也是如此。

脚本标签中的“defer”属性是什么?

脚本标签中的“defer”属性用于指示脚本应在 HTML 文档完全解析后执行。这意味着脚本不会阻塞 DOM 构造,从而加快网页加载时间。但是,这也意味着脚本在 DOM 构造时可能尚未准备好,因此它只能用于不会更改 DOM 结构的脚本。

将脚本移到 HTML 文档底部有何帮助?

将脚本移到 HTML 文档底部可确保它们是最后加载的内容。这意味着网页的其余部分可以在不等待脚本完成加载的情况下进行渲染。但是,此方法只能用于不会更改 DOM 结构的脚本,因为它们在 DOM 构造时可能尚未准备好。

脚本标签中的“async”属性是什么?

脚本标签中的“async”属性用于指示脚本应异步加载。这意味着脚本将在后台加载,而网页的其余部分将继续加载。脚本可以在准备好后立即执行,即使 DOM 尚未完全构建也是如此。这可以大大提高网页加载时间,但它只能用于不会更改 DOM 结构的脚本。

DOM 阻塞对 SEO 的影响是什么?

DOM 阻塞会大大降低网页的加载速度,这会对它的 SEO 排名产生负面影响。像 Google 这样的搜索引擎将网页加载速度视为排名因素之一。因此,避免 DOM 阻塞对于确保您的网页在搜索引擎结果中排名靠前非常重要。

什么是虚拟 DOM 以及它如何提供帮助?

虚拟 DOM 是在 React 等现代 JavaScript 框架中使用的概念。它是实际 DOM 的副本,更改首先在虚拟 DOM 中进行,而不是在实际 DOM 中进行。一旦所有更改都完成,虚拟 DOM 将通过称为协调的过程与实际 DOM 同步。这减少了对实际 DOM 的直接操作次数,从而加快了网页加载时间。

如何检查我的网页是否存在 DOM 阻塞问题?

您可以使用 Google 的 PageSpeed Insights 等工具来检查您的网页是否存在 DOM 阻塞问题。此工具会分析您的网页并提供有关其性能的详细报告,包括任何潜在的问题,例如 DOM 阻塞。

CSS 是否也会导致 DOM 阻塞?

是的,CSS 也可能导致 DOM 阻塞。当浏览器遇到 CSS 文件时,它必须停止并加载它才能继续渲染网页。这是因为 CSS 文件可能包含会更改网页外观的样式。为避免这种情况,您可以使用内联关键 CSS 和推迟非关键 CSS 等方法。

以上是如何避免通过Localstorage和其他罪魁祸首阻止DOM的详细内容。更多信息请关注PHP中文网其他相关文章!

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