如何在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)
        })
    }
}

测试结果如下:

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

写在最后

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

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

相关推荐
Aliex_git16 分钟前
跨域请求笔记
前端·网络·笔记·学习
2501_9011478321 分钟前
面试必看:优势洗牌
笔记·学习·算法·面试·职场和发展
李日灐23 分钟前
C++进阶必备:红黑树从 0 到 1: 手撕底层,带你搞懂平衡二叉树的平衡逻辑与黑高检验
开发语言·数据结构·c++·后端·面试·红黑树·自平衡二叉搜索树
37方寸27 分钟前
前端基础知识(Node.js)
前端·node.js
powerfulhell1 小时前
寒假python作业5
java·前端·python
木子啊1 小时前
前端组件化:模板继承拯救发际线
前端
三十_A1 小时前
零基础通过 Vue 3 实现前端视频录制 —— 从原理到实战
前端·vue.js·音视频
前端小菜袅1 小时前
PC端原样显示移动端页面方案
开发语言·前端·javascript·postcss·px-to-viewport·移动端适配pc端
Bella的成长园地1 小时前
为什么c++中的条件变量的 wait() 函数需要配合while 循环或谓词?
c++·面试
We་ct1 小时前
LeetCode 228. 汇总区间:解题思路+代码详解
前端·算法·leetcode·typescript