性能优化与调试技巧|青训营笔记

92 阅读7分钟

我们都知道,在浏览器中,JavaScript负责实现动态的交互效果和复杂的业务逻辑,因此,JavaScript代码的性能也会影响到用户体验和页面加载速度。所以,掌握一些性能优化和调试技巧对于前端开发人员而言是十分必要的,提高JavaScript代码的运行效率和质量也应该是一个前端开发人员书写一段代码时的目标。下面我将从以下三个方面介绍性能优化与调试技巧:

  1. 减少重绘和重排
  2. 使用节流和防抖技术
  3. 使用性能分析工具

1. 减少重绘和重排

重绘(repaint)和重排(reflow)是浏览器渲染页面的两个过程,它们会消耗大量的资源和时间,影响页面的流畅度和响应速度。

重绘是指当元素的外观发生变化,但不影响布局时,浏览器重新绘制元素的过程。例如,改变元素的颜色、背景、边框等。

重排是指当元素的尺寸、位置或者显示状态发生变化,导致布局发生变化时,浏览器重新计算元素的几何属性,并重新布局页面的过程。例如,改变元素的宽高、边距、定位、显示隐藏等。

重排一定会引起重绘,但重绘不一定会引起重排。重排比重绘更耗费性能,因为它会影响到后续元素的布局和渲染。

为了减少重绘和重排,我们可以采取以下一些措施:

  • 尽量避免频繁地修改元素的样式属性,尤其是影响布局的属性。如果需要修改多个属性,可以使用requestAnimationFrame或者setTimeout将修改操作放在一次回调函数中执行,或者使用documentFragment或者display:none先将元素从文档流中移除,再进行批量修改,最后再插入文档流中。
  • 尽量避免使用表格布局,因为表格中的任何一个元素发生变化都会导致整个表格重新布局。
  • 在非必要的情况下,尽量避免使用CSS表达式(expression),因为它会在每次页面渲染时都重新计算值,导致性能下降。
  • 尽量使用transformopacityfilter等不影响布局的属性来实现动画效果,而不是使用lefttopwidthheight等影响布局的属性。
  • 尽量使用绝对定位或者固定定位的元素来实现动画效果,因为它们不会影响其他元素的布局。
  • 尽量避免在低层级的元素上使用z-index属性,因为它会创建一个新的层叠上下文(stacking context),导致该元素及其子元素重新渲染。

对于减少重绘和重排,我们可以使用使用window.requestAnimationFrame函数来进行处理。关于这个函数,可以参考MDN文档MDN: window.requestAnimationFrame。 下面是一个例子:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>使用requestAnimationFrame来减少重绘和重排</title>
  <style>
    /* 设置box元素的样式,宽高为100px,背景色为红色,居中显示 */
    #box {
      width: 100px;
      height: 100px;
      background-color: red;
      margin: 0 auto;
    }
  </style>
</head>
<body>
  <div id="box"></div>
</body>
<script src="./test.js"></script>
</html>
// 获取需要动画的元素
var box = document.getElementById("box");
// 定义旋转角度
var angle = 0;
// 定义回调函数
function rotate() {
  // 增加旋转角度
  angle += 1;
  // 如果超过360度,归零
  if (angle > 360) {
    angle = 0;
  }
  // 使用transform属性来实现旋转效果,不影响布局
  box.style.transform = "rotate(" + angle + "deg)";
  // 使用requestAnimationFrame来请求下一次动画帧,避免频繁修改样式属性
  requestAnimationFrame(rotate);
}
// 调用requestAnimationFrame开始动画
requestAnimationFrame(rotate);

2. 使用节流和防抖技术

节流(throttle)和防抖(debounce)是两种常用的优化技术,它们可以减少函数的执行频率,从而提高性能。

节流是指在一定时间间隔内,只执行函数的第一次或最后一次调用,忽略中间的调用。例如,我们可以使用节流来优化窗口的resize事件或者鼠标的mousemove事件,避免在短时间内触发过多的回调函数。

防抖是指在一定时间间隔内,只执行函数的最后一次调用,取消前面的调用。例如,我们可以使用防抖来优化输入框的keyup事件或者按钮的click事件,避免在用户输入或点击过程中触发过多的回调函数。

为了实现节流和防抖,我们可以使用以下一些方法:

  • 使用setTimeoutclearTimeout来控制函数的执行时间。例如,我们可以在函数开始时设置一个定时器,在定时器到期后执行函数,并清除定时器;或者我们可以在函数结束时设置一个定时器,在定时器到期后执行函数,并取消前面的定时器。
  • 使用Date.now()或者performance.now()来获取当前时间戳,并与上一次执行时间进行比较。例如,我们可以在函数开始时获取当前时间戳,并与上一次执行时间进行比较,如果超过了设定的时间间隔,则执行函数,并更新上一次执行时间;或者我们可以在函数结束时获取当前时间戳,并与上一次执行时间进行比较,如果没有超过设定的时间间隔,则延迟执行函数,并更新上一次执行时间。
  • 使用第三方库或者工具来实现节流和防抖功能。例如,我们可以使用lodash、underscore等库中提供的_.throttle_.debounce方法来简化代码;或者我们可以使用Chrome DevTools中提供的Throttling和Debounce工具来模拟网络延迟和用户输入。

下面是一个使用定时器函数的例子:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>性能优化与调试技巧:探讨如何通过优化JavaScript代码来提高性能</title>
  <style>
    /* 设置窗口的样式,宽高为100%,背景色为灰色 */
    #window {
      width: 100%;
      height: 100%;
      background-color: gray;
    }

    /* 设置输入框的样式,宽度为300px,高度为30px,居中显示 */
    #input {
      width: 300px;
      height: 30px;
      margin: 0 auto;
      display: block;
    }
  </style>
</head>
<body>
  <!-- 创建一个id为window的div元素,作为窗口对象 -->
  <div id="window">
    <!-- 创建一个id为input的input元素,作为输入框对象 -->
    <input id="input" type="text" placeholder="请输入内容">
  </div>
  <!-- 引入js代码 -->
  <script src="script.js"></script>
</body>
</html>
// 使用setTimeout和clearTimeout来实现节流
function throttle(func, delay) {
  // 定义一个变量来存储定时器
  var timer = null;
  // 返回一个新的函数
  return function () {
    // 获取函数的执行上下文和参数
    var context = this;
    var args = arguments;
    // 如果定时器不存在
    if (!timer) {
      // 设置一个定时器,在delay毫秒后执行函数,并清除定时器
      timer = setTimeout(function () {
        func.apply(context, args);
        timer = null;
      }, delay);
    }
  };
}

// 使用setTimeout和clearTimeout来实现防抖
function debounce(func, delay) {
  // 定义一个变量来存储定时器
  var timer = null;
  // 返回一个新的函数
  return function () {
    // 获取函数的执行上下文和参数
    var context = this;
    var args = arguments;
    // 如果定时器存在,取消前面的定时器
    if (timer) {
      clearTimeout(timer);
    }
    // 设置一个新的定时器,在delay毫秒后执行函数,并赋值给定时器
    timer = setTimeout(function () {
      func.apply(context, args);
    }, delay);
  };
}

// 使用节流和防抖来优化窗口的resize事件和输入框的keyup事件

// 定义一个处理窗口resize事件的函数,打印窗口的宽度和高度
function handleResize() {
  console.log(window.innerWidth, window.innerHeight);
}

// 定义一个处理输入框keyup事件的函数,打印输入框的值
function handleKeyup() {
  console.log(document.getElementById("input").value);
}

// 获取窗口对象和输入框对象
var window = document.getElementById("window");
var input = document.getElementById("input");

// 给窗口对象添加resize事件监听,使用节流来控制函数的执行频率,设置时间间隔为500毫秒
window.addEventListener("resize", throttle(handleResize, 500));

// 给输入框对象添加keyup事件监听,使用防抖来控制函数的执行频率,设置时间间隔为500毫秒
input.addEventListener("keyup", debounce(handleKeyup, 500));

使用节流和防抖的意义主要在于:

  • 节流可以保证在一定时间间隔内至少执行一次函数,避免因为频繁触发事件而导致页面卡顿或者响应延迟。
  • 防抖可以保证在一定时间间隔内只执行一次函数,避免因为重复触发事件而导致不必要的计算或者请求。
  • 节流和防抖都可以提高页面的性能和用户体验,减少资源的浪费和网络的拥堵。

3. 使用性能分析工具

  1. Chrome DevTools:利用Chrome浏览器的开发者工具来分析JavaScript代码的性能。可以使用Performance面板记录和分析页面渲染、网络请求和JavaScript执行等方面的性能指标,识别性能瓶颈。
  2. Lighthouse:Lighthouse是一个开源的自动化工具,可检查网页的性能、可访问性、最佳实践和SEO等方面。它提供了有关页面性能改进的建议,并可以生成性能报告。
  3. WebPageTest:WebPageTest是一个在线网页性能测试工具,可以测试网页在不同网络条件下的加载速度并提供详细的性能分析报告。