在浏览器中,频繁的操作 DOM 是非常消耗内存和 CPU 时间,在我们项目开发过程中,或多或少会绑定一些持续触发的事件,如 resize,scroll,mousemove 以及移动端 touchmove 等。如果事件的回调函数较为复杂,就会导致响应跟不上触发,轻则导致浏览器卡顿,重则导致浏览器崩溃。在这种需求下,防抖(Debounce) 和 节流(Throttle) 就是比较合适的解决方案。
函数防抖和节流就是为了处理同一时刻事件的触发频率和事件函数的执行频率这两者关系的。 我们知道 DOM 事件的触发频率是不可控的,因此我们只能控制事件函数的执行频率,只要是没有达到条件要求的事件,都不触发事件函数,通过这一手段,可以极大的优化浏览器的性能。
【1】什么是函数防抖
函数防抖,这里的抖动就是执行的意思,而一般的抖动都是持续的,多次的。假设函数持续多次执行,我们希望让它冷静下来再执行。也就是当持续触发事件的时候,函数是完全不执行的,等最后一次触发结束的一段时间之后,再去执行(触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间)
举例:比如你点击button按钮,1秒内任你单身30年手速点击无数次,他也还是只触发一次。这个1秒就是你指定的周期,每点击一次都会重新计算时间,最后一次点击button按钮1秒后才会执行事件
【2】代码
/**
* func: 实际要执行的函数
* delay: 延迟时间,单位是毫秒(ms)
*/
function debounce(func, delay) {
let timeout
return function() {
clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行。
timeout = setTimeout(() => {
func.apply(this, arguments)
}, delay)
}
}
【3】原理:防抖的原理是在事件触发一定毫秒之后,再执行。我们看一下debounce的代码,当事件被触发之后,首先查找是否有定时器,如果存在,则清空定时器,并重新设置。只有当定时器的延迟时间到了之后,才会执行真正的回调函数
【1】什么是函数节流
节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。(高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率)
【2】代码
/**
*func: 实际要执行的函数
*delay: 执行间隔,单位是毫秒(ms)
*/
function throttle(func, delay) {
let run = true
return function () {
if (!run) {
return // 如果开关关闭了,那就直接不执行下边的代码
}
run = false // 持续触发的话,run一直是false,就会停在上边的判断那里
setTimeout(() => {
func.apply(this, arguments)
run = true // 定时器到时间之后,会把开关打开,我们的函数就会被执行
}, delay)
}
}
【3】原理
节流 Throttle 和防抖实现的效果类似,但是原理有一些些的不同。函数执行的前提条件是开关打开,持续触发时,持续关闭开关,等到setTimeout到时间了,再把开关打开,函数就会执行了。
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
需求:鼠标滑过一个div,触发onmousemove事件,它内部的文字会显示当前鼠标的坐标。
在上边的场景下,我们不希望触发一次就执行一次,这就要用到防抖或节流。
【1】函数防抖
box.onmousemove = debounce(function (e) {
box.innerHTML = `${e.clientX}, ${e.clientY}`
}, 1000)
【2】函数节流
box.onmousemove = throttle(function (e) {
box.innerHTML = `${e.clientX}, ${e.clientY}`
}, 1000)