面试官:防抖有几种?

295 阅读4分钟

本文为学习冴羽大佬的博客时的笔记,原文:JavaScript专题之跟着underscore学防抖

先放面试题

防抖有几种?

  1. 根据执行时机划分:根据函数执行的时机,防抖可以分为两种类型:

    • 延迟执行防抖(默认方式):事件触发后,会等待设定的延迟时间,如果在这段时间内再次触发事件,则重新计时。只有在延迟时间内没有新的触发时,才会执行目标函数。
    • 立即执行防抖:事件触发时,立即执行目标函数,然后在设定的延迟时间内,不允许再次执行。只有延迟时间结束后,才允许重新执行。
  2. 根据实现方式划分:针对不同的应用场景,防抖可以有不同的实现方式,例如:

    • 基本防抖:仅处理基本的延迟执行逻辑,不考虑this、传参和返回值等问题。
    • 完整防抖:除了处理延迟执行逻辑外,还考虑this、传参和返回值等问题,以适应更复杂的场景。
    • 支持立即执行的防抖:在完整防抖的基础上,增加了立即执行的选项,使得防抖函数更灵活。

防抖解决什么问题?

在前端开发中会遇到一些频繁的事件触发,如下所示,为了节约运行资源,可以使用防抖、节流等方式减少执行、优化体验

  1. window 的 resize、scroll
  2. mousedown、mousemove
  3. keyup、keydown

不太恰当举例

想象一个搭公交场景,当公交车到站停车时,司机并不会只上一个乘客就关门发车,这样相当于每个乘客都需要单独等一辆车,非常浪费空间时间!所以正常情况下司机会等一段时间,比如十秒没有新乘客上车就发车,但是等待期间如果有新客上车,司机会重置等待时间,再等十秒。

虽然一般司机不会这样等,但这个过程就像防抖的原理:在一定时间内,只有当触发事件停止一段时间后,函数才会执行。

简易实现

function debounce(func, wait) {
    var timeout;
    return function () {
        clearTimeout(timeout)
        timeout = setTimeout(func, wait);
    }
}

以上是简易的防抖实现,然而离完全实现还需要注意以下问题:

  1. this
  2. 传参
  3. 返回值

这三个问题也是封装高阶函数时的共性问题,还有两个特有问题:

  1. 支持立即执行
  2. 支持取消

下文将一个个解决以上问题

1. this

需要注意以上func执行时的this并不正确,是指向window

var name = 'window'
const obj = {
    name:'obj',
    getName:function(){console.log(this.name)}
}
​
obj.getDebounceName = debounce(obj.getName,300)
​
obj.getName()//obj
obj.getDebounceName()//window

可以用apply修正

function debounce(func, wait) {
    var timeout;
    return function () {
        const context = this
        clearTimeout(timeout)
        timeout = setTimeout(()=>func.apply(context), wait);
    }
}

2. 传参

func作为函数,肯定需要有传参功能

function debounce(func, wait) {
    var timeout;
    return function (...args) {
        const context = this
        clearTimeout(timeout)
        timeout = setTimeout(()=>func.apply(context,args), wait);
    }
}

3. 立即执行

以上都是基于延后执行的防抖,如果不想要延迟,想要促发时立即执行,并且停止连续操作一段时间之后才重新执行

function debounce(func, wait,immediate) {
    var timeout;
    return function (...args) {
        const context = this
        
        if(timeout)clearTimeout(timeout)
        if(immediate){
            //立即执行
            const executeNow = !timeout
            timeout = setTimeout(()=>timeout = null,wait)
            if(executeNow)func.apply(context,args)
        }else{
            //原版 - 延迟执行
        clearTimeout(timeout)
        timeout = setTimeout(()=>func.apply(context,args), wait);
        }
    }
}

4. 返回值

如果不用promise的话,由于延后执行是异步的,直接返回都是undefined,所以这里就只实现立即执行的返回值

function debounce(func, wait, immediate) {
​
    var timeout, result;
​
    return function () {
        var context = this;
        var args = arguments;
​
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) result = func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
        return result;
    }
}

5. 取消

有时想取消等待时间,让下次触发时立即执行,可以添加一个cancel()方法

function debounce(func, wait, immediate) {
​
    var timeout, result;
​
    var debounced = function () {
        var context = this;
        var args = arguments;
​
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) result = func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
        return result;
    };
​
    debounced.cancel = function() {
        clearTimeout(timeout);
        timeout = null;
    };
​
    return debounced;
}

以上是学习防抖时的笔记,感谢再次冴羽大佬