防抖和节流的实现

105 阅读3分钟

本文首发小呆&小萌的情侣博客,两个前端的学习生活分享小天地,欢迎关注收藏。

前言

防抖和节流,在日常的开发过程中会经常用到,而在面试中,也是经常会被问的一道手写题。下面就一起复习一下这两个函数的知识点和实现吧。

知识点

在工作中常见的一些场景,比如input搜索框、监听窗口的resize,元素的拖拽,以及滚动条的监听。这些场景的共同点就是事件会被频繁的触发,而频繁的触发会导致资源和性能的大量消耗。通常我们会使用防抖和节流函数来进行优化,那么什么场景该使用防抖,什么时候该使用节流呢?

共同点区别应用场景
防抖debounce在事件频繁被触发时,只执行最后一次input输入框、窗口resize、button点击
节流throttle减少事件执行的次数有规律的执行拖拽、scroll

防抖debounce实现

通过知识点我们可以知道,防抖函数的作用是在事件被频繁触发时,只执行最后一次。本质上是延迟执行事件的时机,那么我们可以利用定时器来实现它。

// 拆解需求:
// 1. n秒内只能执行一次,所以需要一个setTimeout来控制fn的执行时机。
// 2. 如果触发事件后在n秒内又触发了事件,则重新计算函数延迟执行的时机。
function debounce(fn, delay) {
    var timer = null
    if(timer) clearTimeout(timer)
    timer = setTimeout(() => {
        fn()
    }, delay)
}

上面的代码看似没啥毛病,但实际每次执行的时候,timer都会被重新创建,然后执行完debounce后,里面的变量就会被销毁。这样就会创建出很多个延迟没执行的函数,一到时间就执行了,并不会重新计算时间。

那我们改造一下,把timer提到全局作用域内:

var timer = null
function debounce(fn, delay) {
    if(timer) clearTimeout(timer)
    timer = setTimeout(() => {
        fn()
    }, delay)
}

通过改造函数虽然也能实现防抖的功能,但是如果有多个防抖的事件,就需要定义多个全局变量,写多个函数,显然这并不是一个靠谱的实现。这时我们需要一个私有变量,并且能在外部访问,那闭包就是一个选择,我们接着改造:

function debounce(fn, delay) {
    var timer = null
    return function() {
        if(timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

调用debounce函数时,返回了一个匿名函数,由于该函数中还用到了debounce函数中的timer变量,会导致timer无法释放。这样就创建了一个私有变量并可以在外部进行访问。需要注意的是:在合适的时机对闭包进行清除,否则会一直占用内存。

节流throttle实现

同样的原理,只需稍作修改就可以实现节流throttle函数:

function throttle(fn, delay) {
    var timer = null
    return function() {
        if(timer) return
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        },delay)
    }
}

与防抖函数不同的是,节流函数是按一定频率有规律的执行,那么除了定时器以外,是不是还可以用时间差的方式来实现呢?

function throttle(fn, delay) {
    var last = 0
    return function() {
        var curr = Date.now()
        if(curr - last >= delay) {
            fn.apply(this, arguments)
            last = curr
        }
    }
}

小结

防抖和节流函数,我们只需要掌握它的异同点、实现原理、应用场景即可,工作中通常不需要我们去重复的造轮子。有诸如Loadsh这种现成的开源工具,可以很方便的调用。