如何在ts中书写健全的防抖节流函数?

pid:111245116

相信关于防抖节流函数大家都不陌生,算得上是第一个接触函数装饰器了。

就拿防抖来说,一般遇到的代码都长这样:

ts 复制代码
type AnyFunction = (...args:any) => any  //之后也会用到

const debounce = function (fn: AnyFunction, timeout: number = 0) {
    let timer: any;
    return function (...args: any[]) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn(...args)
        }, timeout)
    }
}

上面的代码至少会产生以下问题:

  • fn函数的执行上下文丢失

    因为fn在调用时没有明确指定上下文,默认是全局上下文

  • fn函数的返回值丢失

    因为嵌套了一层timeout,因此, 内部函数的返回值不能轻易拿到

  • fn函数的语法提示丢失

    因为是返回了一个内部新的函数,因此原本函数的语法提示会丢失

解决fn函数执行上下文丢失问题:

这个解决起来最简单。

  • 在使用防抖函数时传入的fn参数是一个匿名函数。因为匿名函数的执行上下文是在定义时捕获而不是调用时。

  • 防抖函数额外添加一个参数来指定执行上下文。在调用fn函数时, 指定fn函数的上下文即可。

    typescript 复制代码
    const debounce = function (fn: AnyFunction, timeout: number = 0, context?:object) {
        let timer: any;
        return function (...args: any[]) {
            clearTimeout(timer);
            timer = setTimeout(() => {
                fn.apply(context, args);
            }, timeout)
        }
    }

解决fn函数返回值丢失的问题

  • 基于回调函数的形式。 可以在防抖函数中再加一个参数,作为fn函数调用之后的回调函数,在此回调函数中获取返回值:

    typescript 复制代码
    const debounce = function (fn: AnyFunction, timeout: number = 0, context?:object, cb?:AnyFunction) {
        let timer: any;
        return function (...args: any[]) {
            clearTimeout(timer);
            timer = setTimeout(() => {
                const res = fn.apply(context, args);
                cb?.(res);  //将fn结果传入cb函数
            }, timeout)
        }
    }

    实际调用:

    js 复制代码
    const foo = debounce(() => {
        console.log('hello');
        return 'world!';
    }, 3000, {}, (res) => {
        console.log(res);
    });
    
    foo();
    foo();
  • 基于promise的形式。 可以将内部函数的返回值设置为promise, 在promise中获取到返回值:

    typescript 复制代码
    const debounce = function (fn: AnyFunction, timeout: number = 0, context?:object) {
        let timer: any;
        return function (...args: any[]) {
            return new Promise((resolve, reject) => {
                clearTimeout(timer);
                timer = setTimeout(() => {
                    try {
                        resolve(fn.apply(context, args));
                    }catch (e){
                        reject(e);
                    }
                }, timeout)
            })
    
        }
    }

    实际调用:

    ini 复制代码
    const foo = debounce(() => {
        console.log('hello');
        return 'world!';
    }, 3000);
    
    foo();
    
    foo().then(res => {
        console.log(res);
    })

    使用promise的优缺点也显而易见, 优点是可以随时设置不同的then函数, 缺点是返回值被promise包裹了一层,需要在then函数中拿到。

解决fn函数语法提示丢失问题

既然使用了ts,那肯定要好好利用ts强大的代码提示功能。

思考一下防抖函数干了什么?传入一个fn函数,返回一个新的内部函数。 我们要做的就是让返回的函数和传入的fn函数语法提示一模一样。

先来看看我们返回的函数是什么:

javascript 复制代码
return function (...args: any[]) {
    return new Promise((resolve, reject) => {
        clearTimeout(timer);
        timer = setTimeout(() => {
            try {
                resolve(fn.apply(context, args));
            }catch (e){
                reject(e);
            }
        }, timeout)
    })

}

这个函数的参数是any[], 返回值是Promise, 关键就在于如何修改这两个any。 假设传入的fn函数类型是T, ts中:

  • Parameters可以获取某个函数的参数类型
  • ReturnType可以获取到某个函数的返回值类型

因此修改代码如下:

ts 复制代码
const debounce = function <T extends AnyFunction>(fn: T, timeout: number = 0, context?: object) {
    let timer: any;
    return function (...args: Parameters<T>): Promise<ReturnType<T>> {
        clearTimeout(timer);

        return new Promise((resolve, reject) => {
            timer = setTimeout(() => {
                try {
                    resolve(fn.apply(context, args));
                } catch (e) {
                    reject(e);
                }
            }, timeout)
        })
    }
}

测试结果如下:

可以看到保持了原函数的语法提示。

写在最后

实际上,这些问题算是函数装饰器的通病, 因此你可以将这一套用于绝大部分的函数装饰器

看完了防抖函数,那就快试试节流函数吧?✨

相关推荐
李鸿耀2 分钟前
仅用几行 CSS,实现优雅的渐变边框效果
前端
码事漫谈22 分钟前
解决 Anki 启动器下载错误的完整指南
前端
im_AMBER42 分钟前
Web 开发 27
前端·javascript·笔记·后端·学习·web
聪明的笨猪猪1 小时前
Java Redis “缓存设计”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
蓝胖子的多啦A梦1 小时前
低版本Chrome导致弹框无法滚动的解决方案
前端·css·html·chrome浏览器·版本不同造成问题·弹框页面无法滚动
玩代码1 小时前
vue项目安装chromedriver超时解决办法
前端·javascript·vue.js
訾博ZiBo1 小时前
React 状态管理中的循环更新陷阱与解决方案
前端
StarPrayers.2 小时前
旅行商问题(TSP)(2)(heuristics.py)(TSP 的两种贪心启发式算法实现)
前端·人工智能·python·算法·pycharm·启发式算法
聪明的笨猪猪2 小时前
Java Redis “运维”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
一壶浊酒..2 小时前
ajax局部更新
前端·ajax·okhttp