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

112 阅读11分钟

首先是: 重绘(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。

使用requestAnimationFrame来减少重绘和重排 /* 设置box元素的样式,宽高为100px,背景色为红色,居中显示 */ #box { width: 100px; height: 100px; background-color: red; margin: 0 auto; }

// 获取需要动画的元素 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);

其次:

  1. 使用节流和防抖技术 节流(throttle)和防抖(debounce)是两种常用的优化技术,它们可以减少函数的执行频率,从而提高性能。 节流是指在一定时间间隔内,只执行函数的第一次或最后一次调用,忽略中间的调用。例如,我们可以使用节流来优化窗口的resize事件或者鼠标的mousemove事件,避免在短时间内触发过多的回调函数。 防抖是指在一定时间间隔内,只执行函数的最后一次调用,取消前面的调用。例如,我们可以使用防抖来优化输入框的keyup事件或者按钮的click事件,避免在用户输入或点击过程中触发过多的回调函数。 为了实现节流和防抖,我们可以使用以下一些方法: 使用setTimeout和clearTimeout来控制函数的执行时间。例如,我们可以在函数开始时设置一个定时器,在定时器到期后执行函数,并清除定时器;或者我们可以在函数结束时设置一个定时器,在定时器到期后执行函数,并取消前面的定时器。 使用Date.now()或者performance.now()来获取当前时间戳,并与上一次执行时间进行比较。例如,我们可以在函数开始时获取当前时间戳,并与上一次执行时间进行比较,如果超过了设定的时间间隔,则执行函数,并更新上一次执行时间;或者我们可以在函数结束时获取当前时间戳,并与上一次执行时间进行比较,如果没有超过设定的时间间隔,则延迟执行函数,并更新上一次执行时间。 使用第三方库或者工具来实现节流和防抖功能。例如,我们可以使用lodash、underscore等库中提供的_.throttle和_.debounce方法来简化代码;或者我们可以使用Chrome DevTools中提供的Throttling和Debounce工具来模拟网络延迟和用户输入。 下面是一个使用定时器函数的例子:
性能优化与调试技巧:探讨如何通过优化JavaScript代码来提高性能 /* 设置窗口的样式,宽高为100%,背景色为灰色 */ #window { width: 100%; height: 100%; background-color: gray; } /* 设置输入框的样式,宽度为300px,高度为30px,居中显示 */ #input { width: 300px; height: 30px; margin: 0 auto; display: block; }
// 使用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) 等。 底部信息栏部分,显示了当前选中的任务或函数的详细信息,如名称、耗时、文件位置等。

4.减少DOM访问

如上所述,频繁访问DOM元素可能会导致性能问题。为了避免这种问题,我们应该尽量减少对DOM元素的访问。例如,我们可以缓存对DOM元素的引用,以避免重复访问。此外,我们可以使用documentFragment来减少对DOM的操作次数。

在JavaScript中,访问DOM是一项耗时的操作。每次访问DOM都会导致浏览器重新计算页面布局和重新渲染页面,这会降低Web应用程序的性能。为了提高性能,我们应该尽量减少DOM访问的次数,并将DOM访问的结果缓存起来以便重复使用。

例如,假设我们需要在页面上找到一个元素,并设置它的样式。如果我们使用以下代码来访问DOM,那么每次访问都会重新计算页面布局和重新渲染页面:

document.querySelector('.my-element').style.color = 'red'; 为了避免这种情况,我们可以先将元素的引用存储在一个变量中,并在必要时使用它来访问元素。例如:

const myElement = document.querySelector('.my-element'); myElement.style.color = 'red'; 在上面的代码中,我们首先将元素的引用存储在myElement变量中,并在必要时使用它来访问元素。这样可以避免多次访问DOM,并提高Web应用程序的性能。

5.使用事件委托

事件委托是一种JavaScript技术,它可以减少事件处理程序的数量,从而提高Web应用程序的性能。事件委托是将事件处理程序附加到父元素上,而不是将事件处理程序附加到每个子元素上。当事件在子元素上触发时,它们会冒泡到父元素,然后由父元素处理。这样,我们可以使用一个事件处理程序来处理多个子元素的事件。

例如,假设我们有一个ul元素,它包含多个li元素。如果我们想要在每个li元素被单击时执行一些代码,我们可以使用事件委托来实现:

const ul = document.querySelector('ul'); ul.addEventListener('click', (event) => { if (event.target.tagName === 'LI') { // 在这里处理li元素的单击事件 } });

在上面的代码中,我们将单击事件处理程序附加到ul元素上。当单击事件在li元素上触发时,它们会冒泡到ul元素,然后由ul元素处理。在事件处理程序中,我们可以检查事件的目标元素是否是li元素,并在必要时执行相应的代码。使用事件委托可以减少事件处理程序的数量,从而提高Web应用程序的性能。此外,它还可以使代码更加简洁和易于维护。 6.避免内存泄漏

如上所述,JavaScript中存在内存泄漏的问题,这会导致Web应用程序的性能下降甚至崩溃。内存泄漏是指在程序中创建的对象没有被正确释放,导致内存占用持续增加,直到达到浏览器的内存限制。

为了避免内存泄漏,我们应该避免创建不必要的全局变量和闭包,并且应该及时删除不再需要的对象。例如,当我们在JavaScript中创建一个对象时,我们应该确保在使用完该对象后及时将其删除:let myObj = { // 对象的属性和方法 }; // 在这里使用myObj // 使用完myObj后,删除它 myObj = null;

在上面的代码中,我们在使用完myObj对象后将其设置为null,以释放对象所占用的内存。