JavaScript性能优化与调试 | 青训营
我们为什么要关注性能呢?
性能是创建网页或应用程序时最重要的一个方面。想想我们平时使用应用或者网页的经历就很容易的发现,我们是需要及时的反馈的,如果我点击进入之后,2到3秒没有反应我的体验感基本就会下降到30%以下。没有人想要应用程序崩溃或者网页无法加载,或者用户的等待时间过长。根据有关统计,47%的访问者希望网站在不到 2 秒的时间内加载,如果加载过程需要 3 秒以上,则有 40%的访问者会离开网站。而且随着各种技术的出现,网站的响应速度越来越快,用户的的要求也随之提高,如果不能及时响应,大概率用户会选择换一种方式。
因此,提高性能是每个开发者都要关心的问题。JavaScript是一种高级的、解释性的脚本语言,它执行速度比编译型语言慢。因此,我们需要优化我们的JavaScript代码以提高性能。本文将从减少重绘和重排、节流和防抖、使用性能分析工具以及适当使用异步方式这几个方面来讨论如何通过JavaScript代码来提高性能。
减少重绘和重排
浏览器下载完页面中的所有组件——HTML标记、JavaScript、CSS、图片之后会解析生成两个内部数据结构——DOM树
和渲染树
。
DOM树表示页面结构,渲染树表示DOM节点如何显示。DOM树中的每一个需要显示的节点在渲染树种至少存在一个对应的节点(隐藏的DOM元素disply值为none 在渲染树中没有对应的节点)。渲染树中的节点被称为“帧”或“盒",符合CSS模型的定义,理解页面元素为一个具有填充,边距,边框和位置的盒子。一旦DOM和渲染树构建完成,浏览器就开始显示(绘制)页面元素。
当DOM的变化影响了元素的几何属性(宽或高),浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为重排。完成重排后,浏览器会重新绘制受影响的部分到屏幕,该过程称为重绘。由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。但table及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。
重绘和重排是导致性能下降的主要原因之一。在我们的代码中,我们应该尽量避免频繁地修改DOM元素和样式。相反,我们可以使用CSS3动画来实现元素的变化,使用transition
属性来平滑过渡元素的变化,从而减少重绘次数。
以下是一个例子,该例子演示了在修改样式时如何减少重绘次数:
const element = document.querySelector('#myElement');
// 不推荐方式:每次修改样式都会导致重绘和重排
element.style.color = 'red';
element.style.backgroundColor = 'yellow';
element.style.fontSize = '16px';
// 推荐方式:使用 CSS3 动画一次性修改样式
element.classList.add('animate');
在不推荐的方式中,我们在每次修改样式时都会导致重绘和重排,因为每次修改都会触发浏览器重新计算页面布局。而在推荐的方式中,我们使用CSS3动画来一次性修改样式,从而减少重绘次数。通过添加一个animate类,我们可以使用CSS3动画来平滑过渡元素的变化。
节流和防抖
浏览器的 resize
、scroll
、keypress
、mousemove
等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能
想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应
假设电梯有两种运行策略 debounce
和 throttle
,超时设定为 15 秒,不考虑容量限制
电梯第一个人进来后,15 秒后准时运送一次,这是节流
电梯第一个人进来后,等待 15 秒。如果过程中又有人进来,15 秒等待重新计时,直到 15 秒后开始运送,这是防抖
节流和防抖是优化一些频繁触发的事件处理的常用技巧。节流是指延迟执行函数,只在指定的时间间隔内执行一次。而防抖是指延迟执行函数,只在某个连续触发事件结束后执行一次。通过使用节流和防抖,我们可以减少函数的调用次数,提高性能。
以下是一个例子,该例子演示了如何使用节流来优化滚动事件的处理:
// 常规写法:滚动事件处理函数会频繁执行
window.addEventListener('scroll', () => {
// 处理滚动事件的代码
});
// 使用节流优化:滚动事件处理函数在 100 毫秒内最多执行一次
const throttle = (callback, delay) => {
let timer = null;
return () => {
if (!timer) {
timer = setTimeout(() => {
callback();
timer = null;
}, delay);
}
};
};
window.addEventListener('scroll', throttle(() => {
// 处理滚动事件的代码
}, 100));
在常规写法中,滚动事件处理函数会频繁执行,因为滚动事件可能会连续触发。而在使用节流优化的写法中,我们使用throttle
函数将滚动事件处理函数封装起来,并在100毫秒内最多执行一次。通过使用setTimeout
函数和闭包,我们实现了节流的效果,从而减少了函数的调用次数。
使用性能分析工具
使用性能分析工具可以帮助我们更好地了解代码的执行情况并对其进行优化。一种常见的性能分析工具是浏览器的开发者工具,如Chrome的开发者工具。这些工具提供了一系列有用的性能分析功能,如计时器、CPU使用情况、内存使用情况等。我们可以使用这些功能来检测潜在的性能问题并进行优化。
以下是一个例子,该例子演示了如何使用Chrome的开发者工具来分析JavaScript代码的性能:
console.time('myFunction'); // 开始计时
// 执行你的代码
myFunction();
console.timeEnd('myFunction'); // 结束计时并输出执行时间
我们使用console.time
函数和console.timeEnd
函数来计算代码的执行时间。在开始计时之前,我们使用console.time
函数传入一个标识符,用于区分不同的计时器。在代码执行完毕后,我们使用console.timeEnd
函数传入相同的标识符,以结束计时并输出执行时间。
适当使用异步方式
适当地使用异步方式也是优化性能的一种方式。通过使用异步方式,我们可以在等待网络请求返回时允许其他代码继续执行,避免阻塞用户界面。在JavaScript中,我们可以使用fetch
函数来进行网络请求。通过使用Promise
对象和then
方法,我们可以在网络请求完成后执行相应的回调函数,实现异步处理。
以下是一个例子,该例子演示了如何使用fetch
函数进行异步网络请求:
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
// 处理返回的数据
})
.catch(error => {
// 处理错误
});
我们使用fetch
函数传入一个URL来进行网络请求。在fetch
函数返回一个Promise
对象后,我们使用then
方法来处理异步请求的响应。在第一个then
方法中,我们使用response.json()
方法将响应转换为JSON格式的数据。在第二个then
方法中,我们将处理返回的数据。如果发生错误,我们可以使用catch
方法来处理错误。
综上所述,通过减少重绘和重排、节流和防抖、使用性能分析工具以及适当使用异步方式,我们可以在JavaScript代码中优化性能,提高Web应用程序的响应性和用户体验。这些优化技巧应根据具体的应用场景和需求进行选择和使用。