性能优化秘籍:掌握防抖与节流,让你的前端应用飞起来!
性能优化秘籍:掌握防抖与节流,让你的前端应用飞起来!
在前端开发中,性能优化是一个永恒的话题。其中,防抖(Debounce)和节流(Throttle)是两种常用的技术手段,它们能够有效地减少不必要的函数调用,提升应用的响应速度和用户体验。本文将深入探讨这两种技术的原理、应用场景,并通过具体的代码示例帮助读者掌握它们的使用方法。
防抖(Debounce)
含义
防抖是指在指定时间内连续触发同一事件时,只执行最后一次事件处理函数的技术。它通过设置一个延迟器(setTimeout),在每次事件触发时清除之前的延迟器并重新设置,从而确保在指定时间内只执行一次事件处理函数。
使用场景
- 输入框搜索建议:当用户在搜索框中输入内容时,我们希望在用户停止输入后才发送请求获取搜索建议,避免每次输入都发送请求,减少服务器压力。
- 窗口大小调整:在调整浏览器窗口大小时,resize 事件会频繁触发。使用防抖技术可以避免频繁执行调整布局的代码,提高性能。
- 表单提交:防止用户多次快速点击提交按钮,确保表单只提交一次。
demo实现案例:输入框的联想提示功能
function debounce(func, interval) {
let timer = null;
let _this = this;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(_this, [...args]);
}, interval);
};
}
// 示例:输入框搜索建议
const searchInput = document.getElementById('search-input');
const searchSuggestions = debounce(() => {
const inputValue = searchInput.value;
// 发送请求获取搜索建议 ...
}, 500);
searchInput.addEventListener('input', searchSuggestions);
节流(Throttle)
含义
节流是指在指定时间内连续触发同一事件时,只执行一次事件处理函数的技术。它通过设置一个计时器和一个标志位,确保在指定时间内只执行一次事件处理函数,直到时间间隔结束。
使用场景
- 滚动事件监听:在页面滚动时,我们需要监听页面滚动的位置,但滚动事件会频繁触发。使用节流技术可以减少执行次数,避免卡顿。
- 鼠标拖拽:在实现鼠标拖拽功能时,mousemove 事件会频繁触发。使用节流技术可以提高拖拽性能。
- 动画效果:在创建动画效果时,需要控制动画的帧率,避免过度渲染。
demo实现案例:鼠标拖拽事件
HTML代码
<div id="animated-element" class="animated-element" style="width: 50px; height: 50px;
background-color: #0f0; position: absolute;">
</div>
JS代码
function throttle(func, interval) {
let timer = null;
let _this = this;
return function (...args) {
if (timer) {
return;
}
timer = setTimeout(() => {
func.apply(_this, [...args]);
timer = null;
}, interval);
};
}
const draggable = document.getElementById('draggable');
let isDragging = false;
let startX, startY, startMouseX, startMouseY;
function handleMouseDown(event) {
isDragging = true;
startX = draggable.offsetLeft;
startY = draggable.offsetTop;
startMouseX = event.clientX;
startMouseY = event.clientY;
}
function handleMouseMove(event) {
if (isDragging) {
const deltaX = event.clientX - startMouseX;
const deltaY = event.clientY - startMouseY;
draggable.style.left = `${startX + deltaX}px`;
draggable.style.top = `${startY + deltaY}px`;
}
}
function handleMouseUp() {
isDragging = false;
}
const throttledMouseMoveHandler = throttle(handleMouseMove, 10);
draggable.addEventListener('mousedown', handleMouseDown);
draggable.addEventListener('mousemove', throttledMouseMoveHandler);
draggable.addEventListener('mouseup', handleMouseUp);
另外,如果使用requestAnimationFrame替代setTimeout,可以获得更好的渲染性能,同时也能有更流畅的拖拽动画效果。
题外话:
requestAnimationFrame
requestAnimationFrame 是一个浏览器 API,用于在浏览器的下一次重绘之前执行动画。
为什么能提升拖拽时渲染的性能
- 与浏览器的重绘同步:requestAnimationFrame 会在浏览器的下一次重绘之前执行你的回调函数,这意味着它可以与浏览器的刷新率同步。大多数现代浏览器的刷新率是 60 次/秒,所以 requestAnimationFrame 会尽量在每一帧(大约 16.67 毫秒)执行一次回调函数。这样可以避免不必要的重绘和回流,从而提高性能。
- 优化资源使用:当浏览器标签页不是活动的(例如,用户切换到其他标签页)时,requestAnimationFrame 会暂停执行,从而节省 CPU、GPU 资源和电力消耗。相比之下,setTimeout 或 setInterval 会在后台继续执行,即使用户没有看到动画效果。
- 提高动画的流畅性:由于 requestAnimationFrame 与浏览器的重绘同步,它可以确保动画的每一帧都在正确的时间执行,从而实现更流畅的动画效果。这对于拖拽操作尤为重要,因为拖拽时需要实时更新元素的位置,如果动画不够流畅,用户会感受到明显的卡顿和延迟。
使用 setTimeout 节流的局限性
- 时间间隔不精确:setTimeout 的实际执行时间可能会比设定的时间晚,因为受到任务队列和执行栈的影响。这可能导致拖拽时元素的位置更新不够平滑,出现卡顿现象。
- 资源消耗较高:即使页面被隐藏或最小化,setTimeout 仍然会在后台执行,浪费资源。
- 可能影响性能:如果 setTimeout 的回调函数执行时间过长,可能会导致多个回调函数同时执行,造成性能问题。
如何使用requestAnimationFrame实现拖拽
只需要把上文中的handleMouseMove函数稍作修改
function handleMouseMove(event) {
if (isDragging) {
const deltaX = event.clientX - startMouseX;
const deltaY = event.clientY - startMouseY;
requestAnimationFrame(() => {
draggable.style.left = `${startX + deltaX}px`;
draggable.style.top = `${startY + deltaY}px`;
});
}
}
然后再绑定到dom元素上即可
draggable.addEventListener('mousemove', handleMouseMove);
防抖与节流的区别与选择
- 触发时机:防抖是在指定时间内连续触发事件后,只执行最后一次事件处理函数;而节流是在指定时间内,无论触发多少次事件,只执行一次事件处理函数。
- 使用场景:防抖适用于需要在事件停止触发后执行的场景,如输入框搜索建议;节流适用于需要控制事件触发频率的场景,如滚动事件监听。
- 性能影响:防抖可以减少不必要的事件处理函数执行,降低服务器请求次数;节流可以避免频繁执行导致的性能问题,提高页面流畅度。
用时间轴加以说明
防抖
防抖的情况下,某一次触发会不会执行回调函数是根据下次触发行为的时机来判断的
举例说明
(横线是时间轴,箭头表示触发行为,绿色的箭头表示行为引起了回调函数的执行,红色箭头表示行为未引起回调函数执行)
解释
首先1不会执行回调函数,因为它与下次触发行为(2)的间隔小于interval
2不会执行,因为它与下次触发行为(3)的间隔小于interval
3会执行,因为它与下次触发行为(4)的间隔大于interval
4不会执行,因为它与下次触发行为(5)的间隔小于interval
5会执行,因为在5之后的interval时间内没有触发行为
节流
节流的情况下,某一次触发会不会执行回调函数,是根据上次执行回调函数的时机来判断的
(横线是时间轴,箭头表示触发行为,绿色的箭头表示行为引起了回调函数的执行,红色箭头表示行为未引起回调函数执行)
解释
首先1会执行回调函数,因为它是第一次触发行为
2不会执行,因为它与上次执行回调函数(1)的间隔小于interval
3不会执行,因为它与上次执行回调函数(1)的间隔小于interval
4会执行,因为它与上次执行回调函数(1)的间隔大于interval
5不会执行,因为它与上次执行回调函数(4)的间隔小于interval