提升 JavaScript 性能的实用指南 | 豆包MarsCode AI刷题

113 阅读6分钟

在现代 Web 开发中,JavaScript 的性能对用户体验至关重要。优化性能不仅可以加快网站的加载速度,还能提升用户的交互体验和响应速度。这对于构建用户友好且高效的 Web 应用至关重要。本文将探讨如何通过优化 JavaScript 代码来提升性能,包括减少重绘和重排、使用节流与防抖技术,以及借助性能分析工具等方法。

一、减少重绘和重排

1. 什么是重绘和重排?

  • 重排(Reflow):当 DOM 的变化影响到页面元素的布局时,浏览器需要重新计算这些元素的位置和大小,这个过程称为重排。重排会影响整个页面的布局结构,因此其性能开销较大,特别是当页面包含大量复杂元素时。
  • 重绘(Repaint):当页面元素的外观发生变化而不影响布局时,例如元素的颜色、背景等发生变化时,浏览器会重新绘制这些元素,这个过程称为重绘。虽然重绘的性能开销比重排小,但频繁的重绘同样会影响页面的流畅性。

2. 如何减少重绘和重排?

(1)避免频繁操作 DOM

频繁地操作 DOM 会导致大量的重绘和重排,因此应尽量减少对 DOM 的直接操作,通过批量处理或者将操作合并来减少性能开销。

示例:

不推荐的做法:

const list = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
  const item = document.createElement('li');
  item.textContent = '列表项 ' + i;
  list.appendChild(item);
}

上述代码在循环中多次操作 DOM,导致每次循环都会触发重绘和重排,从而导致性能低下。

优化后的做法:

const list = document.getElementById('list');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const item = document.createElement('li');
  item.textContent = '列表项 ' + i;
  fragment.appendChild(item);
}
list.appendChild(fragment);

使用 DocumentFragment 将多个操作合并为一次,减少了对 DOM 的频繁操作,从而显著降低重排和重绘的次数。

(2)批量修改样式

当需要对同一个元素的多个样式进行修改时,尽量使用 class 替代直接修改 style 属性,或者将所有的样式修改合并在一次操作中完成。这样可以减少由于每次样式修改带来的重绘和重排。

示例:

不推荐的做法:

const element = document.getElementById('box');
element.style.width = '100px';
element.style.height = '100px';
element.style.backgroundColor = 'red';

这种写法每次修改样式都会触发重排和重绘,从而影响性能。

优化后的做法:

const element = document.getElementById('box');
element.classList.add('active');

然后在 CSS 中定义:

.active {
  width: 100px;
  height: 100px;
  background-color: red;
}

通过添加 CSS 类来一次性应用多个样式,减少了浏览器的重绘和重排次数。

(3)避免触发同步布局

读取会触发重排的属性(如 offsetTopoffsetHeight 等)后立即修改样式,会导致强制同步布局。这是因为浏览器需要先计算当前布局,然后才能继续应用新的样式。

示例:

const height = element.offsetHeight;
element.style.height = height + 'px';

在读取和写入之间的这种顺序操作,会导致浏览器多次进行布局计算,影响性能。

优化后的做法是将读取和写入分离开来,避免强制同步布局:

const height = element.offsetHeight;
// 执行其他不相关的操作
setTimeout(() => {
  element.style.height = height + 'px';
}, 0);

通过使用 setTimeout 将写入操作推迟到下一个事件循环,可以避免同步布局带来的性能问题。


二、使用节流和防抖技术

1. 节流(Throttle)

节流是指在一定时间间隔内只执行一次函数,即使在这段时间内函数被多次调用。节流适用于限制某些事件的频繁触发,比如滚动、窗口调整大小等,以确保函数执行的频率在可控范围内。

实现节流函数:

function throttle(func, delay) {
  let timer = null;
  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

使用示例:

window.addEventListener('scroll', throttle(() => {
  console.log('滚动事件触发');
}, 200));

上述代码中,滚动事件的处理函数通过节流技术限制为每 200 毫秒执行一次,从而减少滚动事件的处理频率,提升性能。

2. 防抖(Debounce)

防抖是指在事件触发后,等待一段时间,如果期间没有再次触发事件,才执行函数。防抖适用于输入框的内容变化、窗口大小调整等情景,确保只有在用户停止操作后才进行处理。

实现防抖函数:

function debounce(func, wait) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(this, args);
    }, wait);
  };
}

使用示例:

const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(() => {
  console.log('搜索内容:', searchInput.value);
}, 300));

在上述代码中,用户在输入框中输入内容时,只有在用户停止输入 300 毫秒后,才会触发搜索操作,从而减少不必要的频繁请求。


三、使用性能分析工具

1. 浏览器开发者工具

现代浏览器都提供了性能分析工具,可以帮助开发者识别性能瓶颈,从而进行优化。

步骤:

  1. 打开浏览器的开发者工具(按下 F12)。
  2. 选择“Performance”或“性能”面板。
  3. 点击“Record”按钮开始录制,执行需要分析的操作。
  4. 停止录制,查看性能分析结果,包括重绘、重排和脚本执行的详细信息。

通过这些工具,可以直观地查看页面性能瓶颈所在,比如脚本执行时间过长、重排次数过多等。

2. 使用 Performance API

JavaScript 提供了 Performance API,可以帮助开发者在代码中进行性能测量,精确了解某段代码的执行时间,从而找到需要优化的部分。

示例:

performance.mark('start');

// 需要测量的代码块
for (let i = 0; i < 1000000; i++) {
  // 执行一些操作
}

performance.mark('end');
performance.measure('MyOperation', 'start', 'end');

const measures = performance.getEntriesByName('MyOperation');
console.log('操作耗时:', measures[0].duration, '毫秒');

通过 performance.markperformance.measure,可以轻松地对代码的执行时间进行标记和测量,从而更好地进行性能优化。


四、其他优化技巧

1. 使用 Web Workers

当需要进行大量计算时,可以使用 Web Workers 在后台线程中执行这些计算任务,以避免阻塞主线程,保证页面的流畅性。

示例:

主线程代码:

const worker = new Worker('worker.js');
worker.postMessage('开始计算');
worker.onmessage = function(event) {
  console.log('计算结果:', event.data);
};

worker.js 文件:

onmessage = function(event) {
  // 进行大量计算
  let result = 0;
  for (let i = 0; i < 100000000; i++) {
    result += i;
  }
  postMessage(result);
};

通过将计算任务移到 Web Worker 中,主线程可以继续处理用户的输入和页面渲染,从而保持页面的响应性。

2. 懒加载(Lazy Loading)

对于非首屏内容,使用懒加载技术可以延迟加载这些资源,从而减少初始加载时间,提高页面的加载速度和用户体验。

示例:

<img src="placeholder.jpg" data-src="real-image.jpg" class="lazy-load">
function lazyLoad() {
  const images = document.querySelectorAll('img.lazy-load');
  const viewportHeight = window.innerHeight;
  images.forEach(img => {
    const rect = img.getBoundingClientRect();
    if (rect.top < viewportHeight) {
      img.src = img.getAttribute('data-src');
      img.classList.remove('lazy-load');
    }
  });
}

window.addEventListener('scroll', throttle(lazyLoad, 200));
window.addEventListener('load', lazyLoad);

懒加载可以显著减少初始页面加载的内容量,让用户更快看到页面的主要部分,提升首屏加载的速度。


参考资料: