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函数的上下文即可。
typescriptconst 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函数调用之后的回调函数,在此回调函数中获取返回值:
typescriptconst 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) } }
实际调用:
jsconst foo = debounce(() => { console.log('hello'); return 'world!'; }, 3000, {}, (res) => { console.log(res); }); foo(); foo();
-
基于promise的形式。 可以将内部函数的返回值设置为promise, 在promise中获取到返回值:
typescriptconst 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) }) } }
实际调用:
iniconst 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)
})
}
}
测试结果如下:


可以看到保持了原函数的语法提示。
写在最后
实际上,这些问题算是函数装饰器的通病, 因此你可以将这一套用于绝大部分的函数装饰器
看完了防抖函数,那就快试试节流函数吧?✨