防抖(debounce)与节流(throttle)笔记

206 阅读3分钟

学习笔记,参考以下来源,侵删

定义:

  • 防抖(debounce):短时间间隔地连续触发事件,只执行一次事件的回调函数(触发的时间间隔n<指定的延迟时间delay,则只执行一次)
  • 节流(throttle):固定函数的执行速率;如果持续连续触发事件,则每隔指定的时间(wait)执行一次事件触发的函数

以下为可视化执行过程的网址,以便理解

应用场景:

防抖:

(设定延迟时间delay=300ms)

  • 在mousemove、keydown等连续频繁的事件触发, 300ms没有再次触发事件才调用一次监听函数
  • 表单的实时校验(避免频繁发送验证请求)

节流:

(设定时间间隔wait=100ms)

  • window的resize、scroll的事件监听优化,避免频繁触发,确保在100ms内最多触发一次,
  • mousemove事件统计鼠标移动轨迹,每100ms记录一次鼠标位置

实现:

防抖:

初步实现:

delay时间内,事件最后一次触发后执行一次

/**
 * fn:回调函数
 * delay:延迟时间
 * */
function debounce({fn, delay=300}){
        let timer = null
        return function(){
                if(timer){
                        clearTimeout(timer)
                }
                timer = setTimeout(() => {
                        fn.apply(this, arguments)
                }, delay)
        }
}
//调用
document.addEventListener('mousemove', debounce0({
        fn:function(a){
                console.log('防抖', this, arguments)
        },
        delay:800
}))

优化:

可设置触发后是否立即调用,delay秒之后才能再次调用

/**
 * fn:回调函数
 * delay:延迟时间
 * immediately:触发后是否立即调用一次
 * */
function debounce({fn, delay=300,immediately=true}){
    let timer = null
    return function(){
        if(timer){
                clearTimeout(timer)
        }
        if(immediately){
            // timer为null则执行,否则不执行
            if(!timer){
                    fn.apply(this, arguments)
            }
            // delay时间后再把timer置空(delay时间后才能再次触发)
            timer = setTimeout(() => {
                    timer = null
            }, delay)
        }else{
            // delay时间内最后一次触发执行
            timer = setTimeout(() => {
                    fn.apply(this, arguments)
            }, delay)						
        }
    }
}

// 调用
document.addEventListener('mousemove', debounce({
    fn:function(a){
        console.log('防抖', this, arguments)
    },
    wait:1000
}))

节流

初步实现:

  • 定时器实现:
    • 触发后不会立即执行,在第一次触发wait时间之后才会第一次执行
    • 最后一次触发的时间如果在两次执行时间之间,最后还会执行一次
function throttle({fn, wait=500}){
    let timer = null
    return function(){
        if(!timer){
            timer = setTimeout(() => {
                fn.apply(this, arguments)
                clearTimeout(timer)
                timer = null
            }, wait)
        }
    }
}
document.addEventListener('mousemove', throttle({
    fn:function(a){
         console.log('防抖', this, arguments)
    },
    wait:500
}))

// 调用
document.addEventListener('mousemove', throttle({
    fn:function(a){
        console.log('防抖', this, arguments)
    },
    wait:1000
}))
  • 时间戳实现
    • 首次触发立即执行一次
    • 停止触发后,无论停止时间在哪,都不会再执行。例如,1 秒执行 1 次,在 4.2 秒停止,则第 5 秒不会再执行 1 次
function throttle({fn, wait=300}){
    let previous = 0
    return function(){
        let now = +new Date()
        // 当前时间距离上次执行时间间隔大于wait
        if(now-previous > wait){
            fn.apply(this, arguments)
            // 记录此次执行的时间
            previous = now
        }
    }
}

// 调用
document.addEventListener('mousemove', throttle({
    fn:function(a){
        console.log('防抖', this, arguments)
    },
    wait:1000
}))
  • 结合定时器和时间戳实现
    • 第一次触发立即执行
    • 定时器用来设置在最后退出时增加一个延时执行
function throttle({fn, wait = 300}){
    let previous = 0,
    timer = null
    return function(){
        let now = +new Date()
        // remaining以防客户端修改时间出现now < previous
        let remaining = wait - (now - previous)
        if(remaining <= 0 || remaining > wait){
            fn.apply(this, arguments)
            previous = now
            if(timer){
                clearTimeout(timer)
                timer = null
            }
        }else if(!timer){
            // 最后一次触发设置延时执行
            timer = setTimeout(() => {
                previous = +new Date()
                fn.apply(this, arguments)
                clearTimeout(timer)
                timer = null
            }, remaining)			
        }
    }
}

// 调用
document.addEventListener('click', throttle({
    fn:function(a){
        console.log('防抖', this, arguments)
    },
    wait:1000
}))