大家好,我是Electrolux。今天这个文章讲一讲我ts工具库的一个利器-函数增强
什么是函数增强
我也不知道这个词有没有,没有就当作造了一个词语出来吧哈哈。这玩意跟函数劫持和函数化编程的compose的概念差不多。可以用于修改和扩展现有的 JavaScript 函数。但是区别在于compose基本上是属于嵌套关系。由于嵌套关系,因此常常比较难以理解。
这个函数方法是我从我的工具库里面抽离出来的一个函数。区别于传统的compose,我这里引用了优先级这一个概念进行扁平化处理。这个方法允许你劫持一个函数,或者进行多个函数在同一平面的组合。添加额外的逻辑,然后返回一个经过增强的新函数
它通常包括以下几个要素:
- 一个目标函数:这是你想要增强的函数。
- 一个或多个增强函数:这些函数添加额外的逻辑或修改目标函数的行为。
- 一个优先级概念:用于确定增强函数的执行顺序。
实现函数增强
我们将使用 TypeScript 来实现一个简单的函数增强器。这个函数增强器包含以下核心部分:
fnType
接口:用于定义函数类型,包括优先级和函数本身。enhance
类:主要的增强器类,用于管理目标函数和增强函数。addFunction
方法:用于添加增强函数和它们的优先级。next
方法和asyncNext
方法:用于执行函数增强器中的所有函数,根据优先级排序执行。
下面是一个简单的函数增强器示例:
typescript
/**
* @des 可以看作是函数增强 | 保存劫持函数 | 添加逻辑 | 返回enhance后的增强逻辑
*/
interface fnType {
priority: number;
fn: Function
}
class enhance {
fnArray: Array<fnType>
enhance: Record<any, any>
/**
* @des 被劫持的函数默认优先级是0
*/
constructor(targetFn: Function) {
this.fnArray = [{
priority: 0,
fn: targetFn
}]
/**
* @des 函数共享的变量
*/
this.enhance = {
}
}
addFunction(enhanceFn: Function, priority: number) {
this.fnArray?.push({
fn: enhanceFn,
priority
})
}
// 同步
next(arg: any) {
this.fnArray = this.fnArray?.sort((a, b) => {
return a.priority - b.priority
})
for (let i = 0; i < this.fnArray?.length; i++) {
this.fnArray[i].fn.call(this, arg)
}
}
// 异步
async asyncNext(arg: any) {
this.fnArray = this.fnArray?.sort((a, b) => {
return a.priority - b.priority
})
for (let i = 0; i < this.fnArray?.length; i++) {
let res = await this.fnArray[i].fn.call(this, arg)
if(res == "stop"){
break
}
}
}
}
简单聊一下这些函数吧
在这个示例中,我们首先创建了一个异步函数 sleepA
,然后创建了一个名为 hello
的增强函数。接着,我们创建了一个函数增强器 enhancer
,将 console.log
作为目标函数,并使用 addFunction
方法将 hello
函数添加到增强器中。
最后,我们使用 asyncNext
方法执行增强器中的函数,并查看增强器的状态。
作为开发者和使用者,需要注意下面几点
- 执行顺序: 增强函数的执行顺序由优先级决定,低优先级的函数将首先执行。
- 上下文绑定 : 确保在增强函数中正确绑定
this
上下文,以访问增强器的状态。 - 异步处理 : 如果增强函数包含异步操作,使用
asyncNext
来确保它们按顺序执行。
我们来一段helloworld 吧
函数埋点 | 增强普通函数
统计 某一个函数执行次数,这里以前我们都是使用闭包来统计,现在我们可以 取到 变量enhance 来获取几个函数共同的 变量。下面的示例中我用console.log作为 需要增强的普通函数并且统计他执行的次数
javascript
async function hello(this: enhance) {
this.enhance.count = this.enhance.count ?? 0
this.enhance.count=this.enhance.count+1
console.log("执行次数:",this.enhance.count)
}
let ftest = new enhance(globalThis.console.log)
ftest.addFunction(hello, 1)
ftest.asyncNext(`\x1B[92m 我是第一次输出 \x1B[0m`)
ftest.asyncNext(`\x1B[92m 我是第二次输出 \x1B[0m`)
ftest.asyncNext(`\x1B[92m 我是第三次输出 \x1B[0m`).then(()=>{
console.log("三次输出完成:",ftest.enhance)
})
输出结果如下
makefile
我是第一次输出
我是第二次输出
我是第三次输出
执行次数: 1
执行次数: 2
执行次数: 3
三次输出完成: { count: 3 }
可以看到我们的console.log 是比较优雅的被我们劫持到了
函数记忆化 | 增强 promise异步
这是我之前写的 函数记忆的工具方法,我们要将他改造成扁平化的函数增强。当然我认为这种闭包的方式足够精简,但是用函数记忆化似乎更方便各个函数更加明确的进行组合。下面是未改造前面的代码
typescript
function memoizeAsync<T>(
func: (...args: any[]) => Promise<T>
): (...args: any[]) => Promise<T> {
const cache: Map<string, Promise<T>> = new Map();
return async (...args: any[]): Promise<T> => {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("触发记忆")
return cache.get(key)!;
}
const resultPromise = func(...args);
cache.set(key, resultPromise);
try {
const result = await resultPromise;
return result;
} catch (error) {
cache.delete(key);
throw error;
}
};
}
async function sleep(time:number): Promise<any> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(time)
}, time);
})
}
let sleepTest = memoizeAsync(sleep)
console.log(sleepTest(1000))
console.log(sleepTest(5000))
console.log(sleepTest(5000))
那么我们来用函数增强实现我们常用的函数吧
typescript
interface EnhancePromise extends enhance{
enhance:{
cache:Map<string, any>
result:any
}
}
// 假装是 api请求
async function api(this: EnhancePromise,time:number): Promise<any> {
console.log("触发了api")
return new Promise((resolve) => {
setTimeout(() => {
let res ={id:time}
this.enhance.cache.set(JSON.stringify(time),res);
resolve(res)
}, time);
})
}
// 缓存
async function cache(this: EnhancePromise,args:any) {
this.enhance.cache = this.enhance.cache ?? new Map()
if(this.enhance.cache.get(JSON.stringify(args))){
this.enhance.result = this.enhance.cache.get(JSON.stringify(args))
return "stop"
}
}
// 主函数
async function main(param:any){
await api.bind(this)(param)
}
// 开始函数增强
let ftest = new enhance(main)
ftest.addFunction(cache, -1)
ftest.asyncNext(1000).then(()=>{
}).then(async ()=>{
ftest.asyncNext(2000)
}).then(()=>{
ftest.asyncNext(1000).then(()=>{
console.log("缓存的数据:",ftest.enhance.result)
})
})
可以看到我们一开始 用 main作为主函数 ,主函数的优先级为0,然后设置了一个cache的函数增强方法放在他前面,优先级给一个-1。
最终输出
css
触发了api
触发了api
缓存的数据: { id: 1000 }
看出来,3次调用模拟api,其中有一次走了缓存,所以输出了两次触发了api