实践:性能优化与调试技巧 | 青训营

57 阅读7分钟

1. 减少重绘和重排:

  • 避免频繁修改DOM,可以先将修改放在一个DocumentFragment中,然后一次性添加到DOM中。
  • 使用CSS的transformopacity来触发硬件加速,减少重绘和重排。
  • 使用CSS的will-change属性来标记即将发生变化的元素,以便浏览器做优化。

在上面的示例中,创建了一个简单的页面,演示了减少重绘和重排的策略。具体来说:

  1. 通过点击“Add Elements”按钮向页面中添加了一些项目。为了减少重绘和重排,使用了文档片段来保存新增的元素,然后一次性将文档片段添加到DOM中。
  2. 在鼠标悬停在项目上时,使用了transformopacity来触发硬件加速,减少了重绘和重排的影响。
  3. 在滚动内容区域时,使用了will-change属性来标记即将发生变化的元素,以便浏览器可以做出优化。

2. 节流和防抖技术:

  • 节流(Throttle):限制事件的触发频率,例如在滚动时只执行每隔一段时间的操作。
  • 防抖(Debounce):在事件触发后等待一段时间再执行操作,如果在等待时间内再次触发,则重新计时。

在上面的示例中,创建了一个简单的页面,演示了节流和防抖技术的应用。具体来说:

  1. 定义了throttledebounce函数,分别用于创建节流和防抖版本的事件处理函数。throttle函数会在一定的时间间隔内只执行一次事件处理函数,而debounce函数会等待一段时间后执行事件处理函数,如果在等待时间内再次触发,会重新计时。
  2. 使用节流版本的事件处理函数来处理“Throttle”按钮的点击事件。在点击按钮后的一秒内,多次点击不会触发处理函数。
  3. 使用防抖版本的事件处理函数来处理“Debounce”按钮的点击事件。在点击按钮后的一秒内,如果多次点击,只会在等待时间结束后执行一次处理函数。

3. 异步加载:

  • 使用异步加载脚本(例如asyncdefer属性)可以加快页面的加载速度,不会阻塞其他资源的加载。

在上面的示例中,通过以下步骤演示了异步加载脚本的应用:

  1. 在HTML中,将<script>标签的async属性设置为true,这将使脚本在加载过程中不会阻塞其他资源的加载,异步执行。
  2. 在JavaScript中,模拟了一个耗时的操作,使用simulateLoading函数返回一个延时执行的Promise。这可以模拟脚本加载时可能涉及的网络请求、计算等操作。
  3. 使用asyncawait关键字,以异步方式执行main函数。main函数模拟了加载过程,等待模拟加载完成后打印加载完成信息。
  4. 在控制台输出中,可以看到脚本开始异步加载,但不会阻塞页面的显示。脚本的执行在模拟加载完成后继续进行。

通过使用异步加载脚本,可以避免脚本的加载阻塞页面显示,从而提高页面的加载速度和用户体验。注意,异步加载脚本可能会导致脚本的执行顺序与加载顺序不一致,因此需要谨慎处理脚本之间的依赖关系。

4. 使用性能分析工具:

  • 使用浏览器的开发者工具,如Chrome DevTools,来分析性能瓶颈、CPU占用等。

    常用步骤:

    1. 打开 Chrome 浏览器并访问你要分析性能的网页。
    2. 打开开发者工具:右键点击页面任意位置,选择“检查”或按下 Ctrl + Shift + ICmd + Option + I
    3. 在开发者工具窗口中,切换到 "Performance"(性能)选项卡。
    4. 点击 "Record"(录制)按钮开始录制性能数据。在这个过程中,进行页面的交互,例如点击按钮、滚动页面等,以捕获一段时间内的性能数据。
    5. 停止录制,然后分析性能数据。在性能图表中,你可以看到关于 CPU 使用率、内存占用、网络请求等方面的信息。
    6. 查看“时间轴”图表,识别是否有长时间的运行脚本或频繁的重绘/重排操作。
    7. 使用鼠标在时间轴图表上选择一个时间段,以查看在此时间段内发生的事件和操作。
    8. 在 "Summary"(汇总)面板中,你可以看到关于页面加载时间、资源使用情况和性能评分等信息。
  • 使用性能分析工具,如Lighthouse和WebPageTest,来评估页面性能,并提供优化建议。

5. 懒加载和预加载:

  • 对于图片、视频等资源,可以使用懒加载(Lazy Loading)来延迟加载,只在需要时加载。

    <!DOCTYPE html>
    <html>
    <head>
      <title>Lazy Loading Example</title>
    </head>
    <body>
      <h1>Lazy Loading Example</h1>
      <img data-src="image.jpg" alt="Lazy Loaded Image">
      <script>
        // 使用 Intersection Observer API 监听元素是否进入可视区域
        const images = document.querySelectorAll('img[data-src]');
        const observerOptions = {
          root: null,
          rootMargin: '0px',
          threshold: 0.5 // 当元素50%进入可视区域时触发加载
        };
    
        const imageObserver = new IntersectionObserver(entries => {
          entries.forEach(entry => {
            if (entry.isIntersecting) {
              const lazyImage = entry.target;
              lazyImage.src = lazyImage.getAttribute('data-src');
              lazyImage.removeAttribute('data-src');
              imageObserver.unobserve(lazyImage);
            }
          });
        }, observerOptions);
    
        images.forEach(image => {
          imageObserver.observe(image);
        });
      </script>
    </body>
    </html>
    

    在上面的示例中,图片元素的 src 属性未设置,而是使用 data-src 属性存储实际图片的 URL。当图片进入可视区域时,通过 Intersection Observer API 将 data-src 的值赋给 src 属性,从而触发加载。

  • 对于将来会用到的资源,可以使用预加载(Preloading)来提前加载,加快用户体验。

    <!DOCTYPE html>
    <html>
    <head>
      <title>Preloading Example</title>
      <link rel="preload" href="video.mp4" as="video">
    </head>
    <body>
      <h1>Preloading Example</h1>
      <video controls>
        <source src="video.mp4" type="video/mp4">
        Your browser does not support the video tag.
      </video>
    </body>
    </html>
    

    在上面的示例中,使用 <link> 元素的 rel 属性设置为 "preload",并通过 as 属性指定预加载资源的类型。在这里,预加载了一个视频资源。

6. 使用虚拟列表和虚拟滚动:

  • 当有大量数据需要展示时,使用虚拟列表和虚拟滚动来减少DOM节点的数量,提高渲染性能。

在上面的示例中,使用一个容器元素 .list-container 来模拟可滚动的列表,其中有一个虚拟列表容器 #virtualList。通过监听容器的滚动事件,根据滚动位置来计算可视区域内需要显示的数据项,并通过设置虚拟列表容器的 transform 属性来实现滚动效果。

虚拟列表和虚拟滚动技术的优点:

  • 只渲染当前可视区域内的数据,减少DOM节点数量,提高渲染性能。
  • 适用于大量数据展示的场景,如聊天记录、长列表等。
  • 可以减少页面的内存占用,提高用户体验。

通过使用虚拟列表和虚拟滚动技术,可以在处理大量数据时保持页面的性能和响应性,提供更好的用户体验。

7. 优化图片和多媒体:

在网页中,图片和多媒体资源可能占据大部分页面的数据大小,因此优化这些资源对于提高页面加载速度和性能至关重要。

使用适当的图片格式

不同的图片格式具有不同的压缩算法和特性。选择适当的图片格式可以减少文件大小,提高加载速度。其中,WebP 格式是一种现代的高效图片格式,它通常比 JPEG 和 PNG 格式的图片具有更小的文件大小和更好的视觉质量。以下是一个使用 WebP 格式的图片优化示例:

<!DOCTYPE html>
<html>
<head>
  <title>Image Optimization Example</title>
</head>
<body>
  <h1>Image Optimization Example</h1>
  <img src="image.webp" alt="Optimized Image">
</body>
</html>

使用响应式图片

为了适应不同屏幕尺寸和设备类型,可以使用响应式图片来加载不同大小的图片。使用 srcsetsizes 属性可以指定不同屏幕尺寸下加载不同分辨率的图片。以下是一个使用响应式图片的示例:

<!DOCTYPE html>
<html>
<head>
  <title>Responsive Image Example</title>
</head>
<body>
  <h1>Responsive Image Example</h1>
  <img
    srcset="image-small.jpg 480w, image-medium.jpg 800w, image-large.jpg 1200w"
    sizes="(max-width: 480px) 100vw, (max-width: 800px) 50vw, 1200px"
    src="image-medium.jpg"
    alt="Responsive Image"
  >
</body>
</html>

在上面的示例中,srcset 属性指定了不同分辨率的图片和其对应的宽度,而 sizes 属性指定了不同屏幕宽度下加载图片的大小比例。

优化图片和多媒体资源的优点:

  • 减少页面加载时间,提高用户体验。
  • 减少网络流量,节省用户流量消耗。
  • 提升页面的性能和加载速度。

8. 使用Web Workers:

Web Workers 是一种在后台线程中执行 JavaScript 代码的机制,可以在主线程不受影响的情况下执行耗时的任务。这有助于提高页面的响应性能,特别是在执行大量计算、数据处理或网络请求时。

创建一个简单的 Web Worker

首先,创建一个名为 worker.js 的 JavaScript 文件,其中包含需要在后台线程中执行的代码。在这个示例中,我们将创建一个计算斐波那契数列的 Web Worker:

worker.js

// 计算斐波那契数列的函数
function calculateFibonacci(n) {
  if (n <= 1) {
    return n;
  }
  return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
}

// 监听主线程发送的消息
self.addEventListener('message', function(event) {
  const number = event.data;
  const result = calculateFibonacci(number);
  
  // 将计算结果发送回主线程
  self.postMessage(result);
});

然后,在主线程中创建一个 Web Worker,通过消息传递与 Web Worker 进行通信,执行耗时的计算操作,并获取计算结果:

// 在主线程中创建 Web Worker
const worker = new Worker('worker.js');

// 发送消息给 Web Worker
const inputNumber = 30;
worker.postMessage(inputNumber);

// 监听 Web Worker 发送的消息
worker.addEventListener('message', function(event) {
  const result = event.data;
  console.log(`斐波那契数列第 ${inputNumber} 项为:${result}`);
});

Web Worker 的应用场景

  • 大量计算:执行复杂的数学计算、数据处理、图像处理等任务。
  • 数据处理:对大量数据进行处理和转换。
  • 后台网络请求:在后台线程中执行网络请求,以避免阻塞主线程。
  • 长时间运行的任务:执行需要较长时间的任务,不影响用户界面的响应性能。

Web Workers 的优点:

  • 提高页面的响应性能,避免主线程阻塞。
  • 充分利用多核处理器,加速计算密集型任务。
  • 增强用户体验,确保页面的流畅性。

需要注意的是,Web Workers 之间的通信通过消息传递进行,因此要注意数据的序列化和反序列化。同时,Web Workers 不适合所有情况,对于一些简单任务可能会带来额外的复杂性。

9. 使用缓存:

使用浏览器缓存是提高网站性能的重要策略之一。通过缓存,可以减少重复的网络请求,加快页面加载速度,提升用户体验。

使用HTTP缓存头部

在服务器响应中设置适当的HTTP缓存头部可以指示浏览器在一段时间内缓存特定资源。以下是常见的HTTP缓存头部:

  • Cache-Control: 控制缓存行为的主要头部。可以设置max-age来指定缓存的有效时间。
  • Expires: 指定资源的过期时间,即资源应该在何时过期。
  • ETag: 实体标签,是资源内容的唯一标识,用于判断资源是否已更改。
  • Last-Modified: 指定资源的最后修改时间,用于判断资源是否已更改。

示例:使用Cache-Control头部来设置缓存时间为一小时。

// 在服务器响应中设置Cache-Control头部
app.get('/styles.css', (req, res) => {
  res.setHeader('Cache-Control', 'max-age=3600'); // 缓存一小时
  res.sendFile(path.join(__dirname, 'styles.css'));
});

使用Service Worker缓存

Service Worker 是一种浏览器特性,可以将资源缓存到本地,甚至在离线状态下提供缓存的资源。通过Service Worker,可以实现更高级的缓存策略,如离线访问和动态缓存。

示例:使用Service Worker缓存静态资源,以便离线访问。

sw.js

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('my-cache').then((cache) => {
      return cache.addAll([
        '/',
        '/styles.css',
        '/script.js',
        '/image.jpg'
      ]);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

在页面中注册Service Worker:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then((registration) => {
      console.log('Service Worker registered with scope:', registration.scope);
    })
    .catch((error) => {
      console.error('Service Worker registration failed:', error);
    });
}

缓存的应用场景

  • 静态资源:对于不经常更改的静态资源,如样式表、图片、字体等,可以使用缓存来减少网络请求。
  • 数据接口:对于数据接口的响应,可以设置适当的缓存策略,以减少服务器压力和提高响应速度。
  • 前端框架和库:缓存前端框架和库的CDN链接,以避免重复加载。

优缺点分析

优点:

  • 提升网站性能,减少重复的网络请求,加快页面加载速度。
  • 提高用户体验,特别是在低网络速度或高延迟的情况下。
  • 可以使用Service Worker实现更高级的缓存策略,如离线访问和动态缓存。

缺点:

  • 需要适当地设置缓存策略,否则可能导致用户看到过期的内容。
  • 更新资源时可能需要处理缓存失效的情况。
  • 对于动态数据,可能需要考虑缓存更新的机制。

10.其它优化方法

  • 移除不必要的库和插件:

    • 仔细评估项目所需,避免加载不必要的库和插件,减少资源的大小。
  • 压缩和混淆代码:

    • 使用压缩工具(如UglifyJS)来减小代码体积,使用混淆工具(如Terser)来改变代码结构。
  • 避免使用全局查询和操作:

    • 尽量避免使用全局查询(如document.querySelector)和全局操作(如innerHTML),因为它们会触发重排。