Day4--字节跳动(实践文章)---性能优化与调试技巧|青训营

90 阅读5分钟

性能优化与调试技巧:探讨如何通过优化JavaScript代码来提高性能,包括减少重绘和重排、使用节流和防抖技术、使用性能分析工具等 优化JavaScript代码可以显著提高网页的性能和用户体验。下面是一些常见的性能优化与调试技巧:

  1. 减少重绘和重排:

    • 重绘:当DOM元素的样式改变时,浏览器会重新绘制该元素及其子元素。尽量避免频繁修改元素样式,可以使用CSS类名一次性添加或删除多个样式,或者使用CSS3动画代替JavaScript动画。
    • 重排:当DOM结构发生改变时,浏览器会重新计算元素的布局。减少频繁的DOM操作,可以将它们合并为一个操作,或者使用DocumentFragment来减少重排次数。

减少重绘和重排是提高网页性能的关键方面之一。重绘和重排会导致不必要的资源消耗,降低页面的渲染性能。下面是针对减少重绘和重排的一些建议:

  1. 使用transform替代topleft

    • 修改元素的topleft属性会触发重排和重绘。如果只是需要移动元素,可以使用transform: translate()来代替,它不会触发重排,只会引起重绘。

/* 使用 transform 替代 top 和 left */
.element {
  position: absolute;
  transform: translate(50px, 100px);
}
  1. 使用visibility替代display: none

    • 将元素从DOM中移除(display: none)会触发重排,而使用visibility: hidden则只会触发重绘,可以在需要隐藏元素时采用这种方式。
/* 使用 visibility 替代 display: none */
.hidden-element {
  visibility: hidden;
}
  1. 批量修改DOM:

    • 避免频繁单独修改多个DOM元素的样式,尽量将多个样式修改合并为一次修改,减少重排次数。
  2. 使用DocumentFragment:

    • 当需要添加大量DOM元素时,可以使用DocumentFragment来减少重排次数。
// 使用 DocumentFragment 添加多个 DOM 元素
const parentElement = document.getElementById('parent');

function addElements() {
  const fragment = document.createDocumentFragment();
  for (let i = 0; i < 100; i++) {
    const element = document.createElement('div');
    element.textContent = 'Element ' + i;
    fragment.appendChild(element);
  }
  parentElement.appendChild(fragment);
}
  1. 避免强制同步布局属性读取:

    • 有些布局属性(例如offsetTopoffsetLeftoffsetWidthoffsetHeight等)的读取会触发强制同步布局,如果在动画或循环中频繁读取这些属性,会导致性能问题。应该尽量避免不必要的读取,或者将读取属性的操作集中到一起,以减少重排次数。
  2. 使用CSS动画和过渡:

    • 使用CSS动画和过渡代替JavaScript动画,因为浏览器可以优化CSS动画和过渡的渲染,减少重排和重绘的次数。
/* 使用 CSS 过渡实现动画 */
.element {
  transition: transform 0.3s ease;
}

.element:hover {
  transform: scale(1.1);
}
  1. 使用requestAnimationFrame进行动画更新:

    • 在更新动画时,使用requestAnimationFrame来调用更新函数,以便将多个动画帧合并在一起执行,减少重排和重绘次数。
function animate() {
  // 更新动画状态
  // ...

  // 请求下一帧动画
  requestAnimationFrame(animate);
}

// 启动动画
requestAnimationFrame(animate);
  1. 使用节流和防抖技术:

    • 节流:通过控制函数的调用频率来降低事件处理的执行次数。可以使用lodash.throttle等库来实现节流,也可以手动编写节流函数。
    • 防抖:当事件被触发后,延迟一定时间再执行处理函数,如果在这个延迟时间内再次触发事件,则重新计时。适用于如搜索框输入时的实时搜索场景。
// 节流函数
function throttle(func, delay) {
  let timerId;
  return function (...args) {
    if (!timerId) {
      timerId = setTimeout(() => {
        func.apply(this, args);
        timerId = null;
      }, delay);
    }
  };
}

// 防抖函数
function debounce(func, delay) {
  let timerId;
  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 使用节流和防抖
const handleScroll = () => {
  // 处理滚动事件
};

window.addEventListener('scroll', throttle(handleScroll, 200));
// 或
window.addEventListener('input', debounce(handleInputChange, 300));

  1. 使用性能分析工具:

    • 浏览器自带的开发者工具:Chrome DevTools、Firefox Developer Tools等提供了性能分析、内存分析等功能,可以帮助检测性能瓶颈和内存泄漏问题。
    • Lighthouse:Lighthouse是一款开源的工具,可用于测试网页的性能、可访问性、最佳实践等,并提供改进建议。
    • WebPageTest:可以在真实浏览器上测试网页的加载速度和性能,并生成详细的性能分析报告。
  2. 使用事件委托:

    • 对于大量的DOM元素事件绑定,可以使用事件委托将事件处理程序绑定到它们的共同祖先上,以减少事件处理程序的数量。
<ul id="parentList">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

// 事件委托
const parentList = document.getElementById('parentList');

function handleItemClick(event) {
  const target = event.target;
  if (target.tagName === 'LI') {
    // 处理点击事件
    console.log(target.textContent);
  }
}

parentList.addEventListener('click', handleItemClick);

  1. 懒加载和预加载:

    • 对于图片、视频等资源,可以使用懒加载技术,延迟加载页面上不可见区域的内容,以加快页面初始加载速度。
    • 预加载可以在页面加载完成后提前加载一些即将需要的资源,提高用户体验。
<img src="placeholder.jpg" data-src="image-to-lazy-load.jpg" alt="Lazy-loaded Image">

function lazyLoadImages() {
  const lazyImages = document.querySelectorAll('img[data-src]');
  lazyImages.forEach((img) => {
    if (img.getBoundingClientRect().top < window.innerHeight) {
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
    }
  });
}

// 页面加载完成后执行懒加载
window.addEventListener('load', lazyLoadImages);
// 或者在滚动时执行懒加载
window.addEventListener('scroll', throttle(lazyLoadImages, 200));

  1. 压缩和合并文件:

    • 使用压缩工具对JavaScript、CSS和图片等静态资源进行压缩,减小文件体积。
    • 合并多个小文件为一个大文件,减少网络请求次数。
  2. 使用Web Workers:

    • Web Workers可以将一些耗时的计算或任务放在后台线程中进行,不阻塞主线程的执行,提高页面响应性能。
// main.js
const worker = new Worker('worker.js');

function onWorkerMessage(event) {
  const result = event.data;
  // 处理Web Worker返回的计算结果
}

worker.addEventListener('message', onWorkerMessage);

// 调用Web Worker进行耗时计算
function performCalculations() {
  // 调用Web Worker进行计算
  worker.postMessage({ data: someData });
}

// worker.js
self.addEventListener('message', (event) => {
  const inputData = event.data.data;
  // 执行耗时计算
  const result = doSomeCalculations(inputData);
  // 将结果发送回主线程
  self.postMessage(result);
});

function doSomeCalculations(data) {
  // 执行耗时的计算任务
  // ...
  return result;
}

  1. 使用缓存:

    • 合理利用浏览器缓存机制,通过设置HTTP缓存头信息来缓存静态资源,减少重复下载。

    使用缓存是优化网页性能的一种重要方式。通过缓存,可以避免重复请求相同的资源,减少网络传输时间和服务器负担,从而加快页面加载速度。下面介绍一些常见的缓存技术:

  2. 浏览器缓存:

    • 浏览器会自动对一些资源进行缓存,例如CSS文件、JavaScript文件、图片等。合理设置资源的缓存头信息可以控制缓存的时间和策略。
    // 设置资源的缓存头信息
    // 缓存时间为一天
    res.setHeader('Cache-Control', 'max-age=86400');
    
  3. HTTP缓存头信息:

    • 使用HTTP缓存头信息,例如Cache-ControlExpiresLast-ModifiedETag等,来指示浏览器如何缓存和重新获取资源。
    // 使用 Cache-Control 和 Expires 指定缓存时间
    res.setHeader('Cache-Control', 'public, max-age=86400'); // 缓存时间为一天
    res.setHeader('Expires', new Date(Date.now() + 86400000).toUTCString());
    
    // 使用 Last-Modified 和 If-Modified-Since 进行资源的条件请求
    const lastModified = new Date('2023-07-30');
    res.setHeader('Last-Modified', lastModified.toUTCString());
    
    const ifModifiedSince = req.headers['if-modified-since'];
    if (ifModifiedSince && new Date(ifModifiedSince) >= lastModified) {
      res.statusCode = 304; // Not Modified
      res.end();
      return;
    }
    
  4. Service Worker缓存:

    • 使用Service Worker可以对网页进行离线缓存,允许用户在离线状态下访问已缓存的内容。Service Worker可以拦截网络请求,从缓存中获取资源,或者将请求发送到服务器并缓存响应。
  5. Web Storage:

    • 使用Web Storage(localStorage和sessionStorage)可以在浏览器中存储少量的数据,以避免频繁的网络请求。localStorage数据在浏览器关闭后依然保留,而sessionStorage数据只在当前会话期间有效。
    // 使用 localStorage 存储数据
    localStorage.setItem('username', 'John');
    
    // 从 localStorage 获取数据
    const username = localStorage.getItem('username');
    
  6. CDN缓存:

    • 使用内容分发网络(CDN)可以将资源分发到全球各地的服务器,使用户可以从距离更近的服务器获取资源,加快资源加载速度。
  7. 避免使用全局变量:

    • 全局变量容易造成命名冲突和内存泄漏,尽量使用模块化的方式来管理代码。
  8. 进行代码优化:

  • 使用更高效的算法和数据结构,减少不必要的计算。
  • 避免不必要的递归和循环。