防抖(debounce)与节流(throttle)

577 阅读3分钟

防抖与节流的定义

防抖与节流都是用来控制某个行为(通常是一个函数)在特定时间内执行多少次的技巧,两者极为相似,但又有细微区别。

  • 防抖是限制某一行为在达到特定时间间隔后执行一次。如果特定时间间隔还没有达到又触发了该行为,就会重新计算间隔时间。实则就是等连续频繁的操作结束后,再等待特定时间执行一次函数。

  • 节流是限制某一行为在特定的时间内至多执行一次。如果在特定时间内重复触发,将不会像防抖一样重新计算间隔时间,也不会重复执行函数,而是在特定时间结束后执行一次。实则是在触发函数行为的特定时间内只允许执行一次函数。

防抖与节流实用的场景

防抖:

  • 每次 resize/scroll 完成后触发统一事件
  • 文本框输入的验证(在连续输入文字的时候,在输入停止后校验)
function fun(){
    console.log('onresize')
}
function debounce(method,context){
    clearTimeout(method.timer);
    method.timer = setTimeout(function(){
        method.call(context);
    },1000);
}
window.onresize = function(){
    debounce(fun,window);
}

本例子在浏览器窗口改变后一秒钟 打印出 onresize,如果是一直挪动改变浏览器窗口,也会等停止挪动后一秒再打印 出onresize。

另外一种防抖的封装函数如下:

const debounce = (fn: () => void, delay: number) => {
    const timerRef = null
    return new Proxy(fn, {
        apply(target, ctx, args) {
            if (timerRef) {
                clearTimeout(timerRef)
                timerRef = null
            }
            timerRef = setTimeout(() => Reflect.apply(target, ctx, args), delay)
        },
    })
}

节流:

  • DOM 元素的拖拽功能实现(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 计算鼠标移动的距离(mousemove)
  • Canvas 模拟画板功能(mousemove)
  • 搜索联想(keyup)
  • 监听滚动事件判断是否到页面底部自动加载更多
 ul { border: 1px solid red; }
 li { padding: 10px; }
 <ul id="throttle">
    <li>测试数据123456789</li>
    <li>测试数据123456789</li>
    <li>测试数据123456789</li>
    <li>测试数据123456789</li>
    <li>测试数据123456789</li>
    <li>测试数据123456789</li>
    <li>测试数据123456789</li>
    <li>测试数据123456789</li>
    <li>测试数据123456789</li>
    ………
</ul>
window.onload = function(){
    let i = 1, 
        canRun = true;

    window.addEventListener('scroll', function(){
        if(!canRun){
            return;
        }
        canRun = false;
        setTimeout( function () {
            console.log(`函数节流${i++}`);
            canRun = true;
        }, 2000)
    })
}

如上例子 在首次触发后的2秒内滚动任意次数,只会打印一次;如果从开始一直滚动,会在从第一滚动开始每间隔2秒后打印出 ‘函数节流…’。

另外一种节流的封装函数如下:

const throttle = (fn: () => void, interval: number) => {
    let startTime = 0
    return new Proxy(fn, {
        apply(target, ctx, args) {
            const now = new Date().getTime()
            if (now - startTime > interval) {
                startTime = now
                return Reflect.apply(target, ctx, args)
            }
        }
    })
}

示例代码参考: 函数防抖与函数节流

可扩展阅读:

实例解析防抖动(Debouncing)和节流阀(Throttling)