性能优化与调试技巧:探讨如何通过优化JavaScript代码来提高性能
我们都知道,在浏览器中,JavaScript负责实现动态的交互效果和复杂的业务逻辑,因此,JavaScript代码的性能也会影响到用户体验和页面加载速度。所以,掌握一些性能优化和调试技巧对于前端开发人员而言是十分必要的,提高JavaScript代码的运行效率和质量也应该是一个前端开发人员书写一段代码时的目标。下面我将从以下三个方面介绍性能优化与调试技巧:
- 减少重绘和重排
- 使用节流和防抖技术
- 使用性能分析工具
下面我们来看第一个。
1. 减少重绘和重排
重绘(repaint)和重排(reflow)是浏览器渲染页面的两个过程,它们会消耗大量的资源和时间,影响页面的流畅度和响应速度。
重绘是指当元素的外观发生变化,但不影响布局时,浏览器重新绘制元素的过程。例如,改变元素的颜色、背景、边框等。
重排是指当元素的尺寸、位置或者显示状态发生变化,导致布局发生变化时,浏览器重新计算元素的几何属性,并重新布局页面的过程。例如,改变元素的宽高、边距、定位、显示隐藏等。
重排一定会引起重绘,但重绘不一定会引起重排。重排比重绘更耗费性能,因为它会影响到后续元素的布局和渲染。
为了减少重绘和重排,我们可以采取以下一些措施:
- 尽量避免频繁地修改元素的样式属性,尤其是影响布局的属性。如果需要修改多个属性,可以使用
requestAnimationFrame
或者setTimeout
将修改操作放在一次回调函数中执行,或者使用documentFragment
或者display:none
先将元素从文档流中移除,再进行批量修改,最后再插入文档流中。 - 尽量避免使用表格布局,因为表格中的任何一个元素发生变化都会导致整个表格重新布局。
- 在非必要的情况下,尽量避免使用CSS表达式(expression),因为它会在每次页面渲染时都重新计算值,导致性能下降。
- 尽量使用
transform
、opacity
、filter
等不影响布局的属性来实现动画效果,而不是使用left
、top
、width
、height
等影响布局的属性。 - 尽量使用绝对定位或者固定定位的元素来实现动画效果,因为它们不会影响其他元素的布局。
- 尽量避免在低层级的元素上使用
z-index
属性,因为它会创建一个新的层叠上下文(stacking context),导致该元素及其子元素重新渲染。
对于减少重绘和重排,我们可以使用使用window.requestAnimationFrame函数来进行处理。关于这个函数,可以参考MDN文档MDN: window.requestAnimationFrame。 下面是一个例子:
html
<!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>
js
// 获取需要动画的元素
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
事件,避免在用户输入或点击过程中触发过多的回调函数。
为了实现节流和防抖,我们可以使用以下一些方法:
- 使用
setTimeout
和clearTimeout
来控制函数的执行时间。例如,我们可以在函数开始时设置一个定时器,在定时器到期后执行函数,并清除定时器;或者我们可以在函数结束时设置一个定时器,在定时器到期后执行函数,并取消前面的定时器。 - 使用
Date.now()
或者performance.now()
来获取当前时间戳,并与上一次执行时间进行比较。例如,我们可以在函数开始时获取当前时间戳,并与上一次执行时间进行比较,如果超过了设定的时间间隔,则执行函数,并更新上一次执行时间;或者我们可以在函数结束时获取当前时间戳,并与上一次执行时间进行比较,如果没有超过设定的时间间隔,则延迟执行函数,并更新上一次执行时间。 - 使用第三方库或者工具来实现节流和防抖功能。例如,我们可以使用lodash、underscore等库中提供的
_.throttle
和_.debounce
方法来简化代码;或者我们可以使用Chrome DevTools中提供的Throttling和Debounce工具来模拟网络延迟和用户输入。
下面是一个使用定时器函数的例子:
html
<!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>
js
// 使用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. 使用性能分析工具
性能分析工具可以帮助我们检测和优化JavaScript代码的性能,它们可以提供以下一些功能:
- 记录和展示代码的执行过程和时间,比如主线程的事件循环、每个事件循环的任务、每个任务的调用栈、每个函数的耗时等。
- 定位和高亮代码中的性能瓶颈和问题,比如内存泄露、过多的重绘和重排、低效的循环和函数等。
- 提供一些优化建议和解决方案,比如使用缓存、节流和防抖、减少全局变量、使用直接量等。
这样的工具有很多,我们主要介绍一下Chrome DevTools 的 Performance。因为这个大家应该都有,就在F12打开就是。
Chrome DevTools 的 Performance 工具是Chrome浏览器内置的一个强大的工具,可以记录和展示网页的性能数据,包括JS代码、CSS样式、DOM操作、网络请求等。它还可以模拟不同的网络和CPU条件,以及提供一些优化建议。
Chrome DevTools 的 Performance 工具主要包括以下几个部分:
- 概览部分,显示了整体的界面渲染情况,每个时间段执行的事件顺序,以及一些关键指标,如页面帧速 (FPS)、CPU 资源消耗、网络请求流量、V8 内存使用量 (堆内存) 等。
- 性能面板部分,显示了渲染进程中各个线程的执行记录,包括主线程 (Main)、合成线程 (Compositor)、光栅化线程池 (Raster)、GPU进程主线程 (GPU) 等。每个线程中可以看到不同类型的任务,如脚本 (Scripting)、渲染 (Rendering)、绘制 (Painting)、系统 (System) 等。每个任务中可以看到不同层级的函数调用栈和耗时。
- 性能摘要部分,显示了在检测性能的时间范围内,各个类型任务所占用的时间比例和总时长,如加载 (Loading)、脚本 (Scripting)、渲染 (Rendering)、绘制 (Painting)、其他 (Other)、空闲 (Idle) 等。
- 性能细节部分,显示了在检测性能的时间范围内,一些关键的时间节点在何时产生的数据信息,如首次内容绘制 (FCP)、最大内容绘制 (LCP)、首次有意义绘制 (FMP)、DOM内容加载事件 (DCL)、页面加载事件 (L) 等。
- 底部信息栏部分,显示了当前选中的任务或函数的详细信息,如名称、耗时、文件位置等。
使用Chrome DevTools 的 Performance 工具的一般步骤如下:
- 点击左上角的黑色按钮或者圆形箭头按钮来触发性能数据记录,黑色按钮可以记录交互阶段的性能数据,圆形箭头按钮用来记录加载阶段的性能数据。
- 点击右上角的停止按钮或者等待自动停止来结束性能数据记录。
- 查看并分析性能数据,可以选择不同的视图模式和过滤条件,也可以放大和缩小时间范围。
- 定位并解决性能问题,可以查看高亮或者标记的部分,并点击跳转到源码位置。
为了方便,可以将开发者工具控制台设置为分离模式: