如何在JavaScript中避免DOM阻塞

JavaScript程序在浏览器中的单个线程和Node.js等运行时运行。当代码在浏览器选项卡中执行时,其他一切都会停止:菜单命令,下载,渲染,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 Promises和ES2017引入async/ await使编码更容易,但回调仍然在表面之下使用。

Blocking Bandits

不幸的是,一些JavaScript操作总是同步的,包括:

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

下面的例子显示了一个入侵者,它使用CSS动画和JavaScript动画的组合来移动肢体。右边的图片是一个基本的GIF动画。按下写入按钮,默认100,000个会话存储操作:

如何在JavaScript中避免DOM阻塞

感兴趣的可以到这里查看:https://codepen.io/SitePoint/pen/GzLPJV

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

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

Web Workers

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

// main.js
// 是否支持web workers?
if (!window.Worker) return;
// 启动web工作者脚本
let myWorker = new Worker('myworker.js');
// 收到来自myWorker的消息
myWorker.onmessage = e => {
  console.log('myworker sent:', e.data);
}
// 发送消息myWorker
myWorker.postMessage('hello');

web worker脚本:

// myworker.js
// 当收到消息时启动
onmessage = e => {
  console.log('myworker received:', e.data);
  // ... 长时间运行的流程 ...
  // 发布消息回来
  postMessage('result');
};

一个worker甚至可以派生其他worker来模拟复杂的、类似线程的操作。然而,workers被故意限制,并且workers不能直接访问DOM或localStorage(这样做将有效地使JavaScript成为多线程的,并破坏浏览器的稳定性)。因此,所有消息都以字符串的形式发送,这允许传递json编码的对象,但不传递DOM节点。

Workers可以访问一些window属性、web sockets和IndexDB——但是他们不会改进上面的示例。在大多数情况下,工作人员被用于长时间运行的计算——比如光线跟踪、图像处理、比特币挖掘等等。

(Node.js提供了类似于web worker的子进程,但是可以选择运行用其他语言编写的可执行程序。)

硬件加速动画

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

默认情况下,上面的示例通过更改left-margin移动入侵者。这一属性以及类似的属性(如left和width)会导致浏览器在每个动画步骤中重绘整个文档。

使用transform和/或opacity属性时,动画更有效。这些有效地把元素放到一个单独的合成层,这样它就可以被GPU独立地动画。

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

内存中的存储

更新内存中的对象要比使用写入磁盘的存储机制快得多。在上面的例子中选择对象存储类型,然后单击write。结果会有所不同,但是它应该比等价的会话存储操作快10倍左右。

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

// 早先保存数据
var store = JSON.parse(localStorage.getItem('store'));
// 初始化一个空存储
if (!store || !store.initialized) {
  store = {
    initialized: true,
    username: 'anonymous'
    score: 0,
    best: { score: 1000, username: 'Alice' }
  }
};
// 保存到卸载页面时的本地存储
window.addEventListener('unload', () => {
  localStorage.setItem('store', JSON.stringify(store));
});

游戏或单页应用程序可能需要更复杂的选项。例如,当:

  • 有几秒钟没有用户活动(鼠标、触摸或键盘事件)
  • 游戏暂停或应用程序选项卡在后台
  • 有一个自然的暂停——比如当玩家死亡,完成一个关卡,在主屏幕之间移动等等。

网络性能

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

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

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

未经允许不得转载:码云笔记 » 如何在JavaScript中避免DOM阻塞
喜欢(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

在线客服

在线客服

  • 扫描二维码,微信联系 扫描二维码,微信联系