JavaScript的防抖和节流

在引入防抖和节流之前,给出一个需求,需要我们监视浏览器的滚动事件,返回滚动条与顶部的距离,我们很容易能写出这样的代码:

1
2
3
4
5
6
function showTop(){
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop
console.log('滚动条距离顶部的距离:' + scroolTop)
}
// 绑定事件
window.onscroll = showTop

但是这样真的可行不?答案是可行但不太合适,因为这个函数的运行频率实在是太高了,只要滚动会一直触发,把浏览器的性能浪费在这里肯定是不合适的。

由此我们引出防抖和节流这两个概念。

防抖

  1. 定义:

    对于短时间内连续触发的事件,防抖的含义就是当持续触发事件时,一定时间段内没有再次触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

  2. 思路:

    在第一次触发事件后,不要立即执行函数,而是给出一个期限值。

    • 假如期限值内没有再次触发事件就执行处理函数
    • 假如期限值内再次触发了该事件,则重新开始计时。
  3. 效果:

    短时间内大量触发同一时间,那么只会执行一次处理函数

  4. 实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // fn: 需要防抖的函数
    // delay: 期限值,ms
    function debounce(fn, delay){
    // 借助闭包避免变量全局污染
    let timer = null
    // 这个才是onscroll绑定的函数
    return function(){
    // 如果已经在计时中,则取消计时并重新开始计时
    if(timer){
    clearTimeout(timer)
    timer = setTimeout(fn, delay)
    // 还未开始计时,则开始计时
    }else{
    timer = setTimeout(fn, delay)
    }
    }
    }
    // 需要防抖的函数
    function showTop(){
    let scrollTop = document.body.scrollTop || document.documentElement.scrollTop
    console.log('滚动条距离顶部的距离:' + scroolTop)
    }
    // 绑定事件
    window.onscroll = debounce(showTop, 1000)

    上述防抖函数的内部if条件语句是可以优化一下的:

    1
    2
    3
    4
    5
    6
    7
    8
    return function(){
    // 不管当前是否开始计时,只要触发了都是要开始计时的
    // 只是如果当前已经开始,则需要先停掉正在进行的计时器
    if(timer){
    clearTimeout(timer)
    }
    timer = setTimeout(fn, delay)
    }

    看起来,好像已经实现需求了,也不会一直触发,避免了性能的过渡损耗

    但是此时又出现了一个新的问题,假如一直滑动滚轮让浏览器滚动,岂不是函数永远不会触发了?

节流

当持续触发事件时,保证一定时间段内只调用一次事件处理函数。

简单说就是,如果短时间内大量触发同一事件,那么在函数执行一次后,该函数在指定的事件期限内不会再执行,直至过了这段时间才生效。

直接看代码吧,更加容易理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function throttle(fn, delay){
// 设定状态valid表示函数当前是否处于可执行工作的状态
// 记住表示的是当前是否可以工作,不是是否正在工作
let valid = true
return function(){
// 如果不可以工作,则直接return
if(!valid){
return false
}
// 如果可以工作,则将其设为不可工作状态,并在等待设定时间之后执行一次处理函数
// 在等待过程中,是不会再执行处理函数的
valid = false
setTimeout(()=>{
fn() // 执行处理函数
valid = true // 处理完之后将其设为可工作状态
},delay)
}
}
//要节流的函数
function showTop(){
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滚动条距顶部距离:'+scrollTop)
}
window.onscroll = throttle(showTop,1000)

当然我们也可以通过时间戳来实现节流操作

1
2
3
4
5
6
7
8
9
10
function throttle(fn, delay){
let prev = Date.now()
return function(){
let now = Date.now() // now保存本次执行的时间
if(now - prev >= delay){
fn()
prev = Date.now() // prev保存执行完的时间
}
}
}
作者

胡兆磊

发布于

2021-11-01

更新于

2022-10-23

许可协议