一. 前言
为什么要做性能优化?性能优化到底有多重要? 性能优是为了提供更好的用户体验 、加快网站加载速度 、提高搜索引擎排名 、节省服务器资源 、适应多种设备和网络环境等方面的需求。通过不断优化性能,可以提高用户满意度、增加网站流量提高业务效果。
同时性能优化是把双刃剑,有好的一面也有坏的一面。好的一面就是可以能提升网站性能,坏的一面就是配置多,代码复杂,或者要遵守的规则太多。并且某些性能优化规则并不适用所有场景,所以也并不是一味的追求性能优化,而是需要谨慎使用。
防抖和节流 是JavaScript
中常用的两种性能优化方式。面试中我们也会经常碰到。它们的作用是减少函数的执行次数,以提高代码的性能。本文将详细介绍防抖 和节流的定义、原理和实现方法,并讨论如何在具体功能中使用它们。
二. 对防抖与节流的理解
什么是防抖(Debounce)
如下图所示,防抖(Debounce
)是指在事件被触发 delay
时间后再执行回调 function
函数,如果在这设置的 delay
时间内事件又被触发,则重新计时。这可以使用在些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
什么是节流(Throttle)
如下图所示,节流(Throttle
)是指规定一个单位时间(延迟 delay
时间),在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll
函数的事件监听上,通过事件节流来降低事件调用的频率。
三. 防抖函数的应用场景
按钮提交场景: 防止多次提交按钮,只执行最后提交的一次 服务端验证场景: 表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能等
以用户搜索请求后台为例,我们来详细看一下:
原始代码,未进行防抖优化
js
<div class="box">
输入事件没有进行防抖处理:<input type="text" id="demo" name="demo">
</div>
<script>
// 模拟请求
function req(value){
console.warn("request: " + value + ", time: " + new Date());
}
const inputBox = document.getElementById("demo");
inputBox.addEventListener("keyup",e=>{
req(e.target.value);
})
</script>
上面结果所示,只要我们在输入框中每次输入文字,那么就会触发一次模拟请求,这对于用户和开发者而言都是不好的体验和资源的浪费。
思考: 我们想到每次用户输入文字都是需要一定时间的,那么我们可以定义在规定时间进行完整输入才能进行请求,这样我们可以减轻对后台的压力。
使用防抖函数优化后
防抖规则:
500ms
内输入文字按下键盘都不会触发请求事件,而是在输入框的定时器500ms
停止输入后发送请求
优化: 我们改造一下上述的代码,监听我们的输入框,在500ms
内连续输入,不进行任何操作,500ms
后发送一次请求。
js
<div class="box">
输入事件进行防抖处理:<input type="text" id="demo" name="demo">
</div>
<script>
// 模拟请求
function req(value){
console.warn("request: " + value + ", time: " + new Date());
}
const inputBox = document.getElementById("demo");
inputBox.addEventListener("keyup",e=>{
debounce(() => req(e.target.value), 500);
})
</script>
从上面的运行结果可以看出,在500ms
内在输入框中连续输入文字都不会触发请求事件,而是在输入框的定时器500ms
停止输入后发送请求。
实现原理很简单,就是对于频繁输入的输入框请求事件添加定时器进行计数,在指定时间内进行频繁输入并不会进行请求,而是在指定时间间隔内停止输入才会执行函数。
当停止输入但在此定时器计数时间内,会重新进行触发请求事件。
四. 节流函数的应用场景
拖拽场景: 固定时间内只执行一次,防止超高频次触发位置变动
缩放场景: 监控浏览器resize
动画场景: 避免短时间内多次触发动画引起性能问题
提交场景: 避免同时多次提交
以按钮提交场景为例子,我们来详细看一下:
原始代码,未进行节流优化
js
<button id="demo" style="margin: 50px;">点击按钮</button></button>
<script>
let value = 1
// 模拟请求
function req(){
console.warn("request: " + value++ + ", time: " + new Date());
}
const ele = document.getElementById("demo");
ele.addEventListener("click", (e) => {
req()
});
</script>
上面结果所示,只要我们点击按钮,那么就会触发一次模拟请求,这除了对于服务器的压力以外,还会造成多次数据提交,有可能会造成数据重复的风险。
使用节流函数优化后
节流规则:
1000ms
内频繁点击按钮,只能成功一次
优化: 按钮频繁多次点击时,为了避免用户多次请求,做了节流限制,在规定一个1000ms
时间内,只能有一次点击成功的触发动作。
js
<button id="demo" style="margin: 50px;">点击按钮</button></button>
<script>
let value = 1
// 模拟请求
function req(){
console.warn("request: " + value++ + ", time: " + new Date());
}
const ele = document.getElementById("demo");
ele.addEventListener("click", (e) => {
throttle(() => req(), 1000)
});
</script>
从上面的运行结果可以看出,在1000ms
内按钮连续多次点击,只有一次成功。
五. 实现防抖函数和节流函数
实现防抖函数
实现思路
- 定义一个计时器变量,默认为null。
- 当事件触发时,清除之前的计时器。
- 创建一个新的计时器,延迟执行目标函数。
- 在在此时间内,如果再次触了事件,则重复步骤2和3。
- 在延迟时间内没有再次触发事件时,执行目标函数。
js
/**
* @desc 防抖函数:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
* @param {Function} func 函数
* @param {Number} wait 延迟执行毫秒数
* @param {Boolean} immediate true 表示立即执行,false 表示非立即执行
*/
let timeout
function debounce(func, wait = 500, immediate = false) {
// 清除定时器
if (timeout) clearTimeout(timeout)
// 立即执行,此类情况一般用不到
if (immediate) {
let callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) typeof func === 'function' && func()
} else {
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === 'function' && func()
}, wait)
}
}
实现节流函数
实现思路
- 定义一个标记变量来表示是否允许执行目标函数,默认为0。
- 当事件触发时,检查当前的时间戳与标记变量的差值,如果差值大于设定的延迟时间,则执行函数并将标记变量设为当前的时间戳。如果差值小于设定的延迟时间,则不执行。
- 在指定时间间隔内再次触发事件时,则重复2步骤。
js
/**
* @desc 节流函数:在一定时间内,只能触发一次
* @param {Function} func 函数
* @param {Number} wait 延迟执行毫秒数
*/
let previous = 0
function throttle(func, wait = 500) {
let now = Date.now()
if (now - previous > wait) {
typeof func === 'function' && func()
previous = now
}
}
总结
前端防抖函数和节流函数都是为了优化页面性能而设计的。
防抖函数和节流函数都可以通过减少不必要的函数执行次数,提高页面性能和响应速度。具体使用哪种函数取决于事件触发的情况和需要执行处理逻辑的需求。在实际应用中,可以根据具体场景选择合适的函数来优化代码。