JavaScript实现函数重载
前言
今天开发一个需求的时候, 遇到了一个类似函数重载的场景。
这个需求可以接收一个下标。
当下标存在时, 执行逻辑A。 当下标不存在时, 执行逻辑B。
看着很简单对吧。
if (index === undefined) {
A()
} else {
B()
}直接一个if else解决, 但是我们可以换种思路, 通过函数重载的方式去实现它, 后续如果参数格式继续变化, 其拓展性也会更好。
因此就打算实现一个函数, 通过js实现函数重载的能力。
知识铺垫
在实现函数重载之前, 先回顾下什么是函数重载? 学过java的朋友当然很熟悉, 但学习前端的同学可能就不太熟悉了, 因为js并不支持函数重载。
什么是函数重载呢?
简单来说就是 同一个作用域内定义多个同名函数但参数个数或类型不同的情况。 针对不同的参数可以定义不同的函数逻辑, 比如:
public class OverloadingExample {
// 函数重载:整数相加
public int add(int a, int b) {
return a + b + 1;
}
// 函数重载:浮点数相加
public double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
OverloadingExample example = new OverloadingExample();
System.out.println("Sum of integers: " + example.add(2, 3));
System.out.println("Sum of doubles: " + example.add(2.5, 3.5));
}
}这里的add函数是存在同名的, 但是二者类型不同, 会去执行不同的代码。
那对于我们常用的js而言, 函数重载是不支持的
所以我们一般会采用条件判断或者对象参数这种方式实现:
// 条件判断
function exampleFunction(param1, param2 = null) {
if (param2 === null) {
// 处理只有一个参数的情况
} else {
// 处理两个参数的情况
}
}// 参数对象
function exampleFunction(options) {
if (object.keys(options).length == 1) {
// 处理只有一个参数的情况
} else {
// 处理多个参数的情况
}
}// 函数名称区分
function exampleFunctionOneParams(param) {
// 处理只有一个参数的情况
}
function exampleFunctionTwoParams(param1, param2) {
// 处理只有两个参数的情况
}代码思路
首先在上面的知识铺垫中, 我们知道函数重载的核心是定义多个同名函数, 通过不同的参数类型或组合执行不同的代码。
思路如下:
- 可以维护一个类, 在类中️一个对象, 对象中函数名做
key,value是一个新的对象。 - 其中新的对象中 又以不同参数类型组合而成字符串作为
key, 而对应函数为value。 - 使用时先实例化class。
- 将目标函数和这个目标函数的参数集合传入, 实例化对象中新增和目标函数同名的函数, 并将目标函数和参数集合注册到映射对象中。
- 使用时, 直接调用实例对象下的目标函数, 由目标函数提供的同名函数去映射到对应的map中注册的真实函数并执行。
代码实现
// 非法赋值标识
let isAssignmentValid = false
class Overload {
constructor() {
this.fnMap = new Proxy({}, {
set: setProxyValid
});
return new Proxy(this, {
set: setProxyValid
});
}
// 重载函数注册
reg(fn, arr = []) {
// 注册函数映射map
if(typeof fn !== 'function') throw new Error(`params err`);
isAssignmentValid = true
const typesKey = arr.join('_');
if (!this.fnMap[fn.name]) {
this.fnMap[fn.name] = new Proxy({}, {
set: setProxyValid
});
}
this.fnMap[fn.name][typesKey] = fn;
// 注册重载实例同名函数
if (!this[fn.name]) {
this[fn.name] = (...args) => {
const typesKey = getParameterTypesKey(...args);
const targetFn = this.fnMap[fn.name][typesKey];
return targetFn(...args);
}
}
isAssignmentValid = false
}
}
// proxy 禁止非法赋值 封装
function setProxyValid(target, property, value) {
if(isAssignmentValid) {
target[property] = value
return true
} else {
throw new Error(`Cannot set attribute`);
}
}
// 获取参数类型key
function getParameterTypesKey(...args) {
const parameterTypes = args.map(arg => typeof arg);
const parameterTypesKey = parameterTypes.join('_');
return parameterTypesKey;
}
// 导出工厂函数
export function OverloadFactory() {
return new OverloadJS()
}代码解释
这段代码是一个 JavaScript 的重载函数。 它允许你定义同名函数但参数不同的多个版本, 然后根据传入的参数类型来自动调用对应的函数版本。
步骤一
代码先定义了一个 isAssignmentValid 标识, 表示当前能否对重载实例或类进行赋值。
步骤二
代码的构造函数中会创建一个proxy对象 fnMap, 这个对象的作用是存储函数和参数集合间的映射关系。
构造函数最后会返回一个指向this对proxy对象, 后续的注册和函数调用都会在这个proxy实例上进行。
注:(代码中所有的proxy对象, 会使用 setProxyValid 函数对set操作进行封装, 禁止非法赋值, 防止开发时开发人员误赋值, 导致重载实例异常。)
步骤三
代码中的reg是重载函数的注册函数也是核心方法。
主要分成两部分
PART1:(注册函数映射map)
注册函数接受两个参数一个是需要重载的目标函数, 一个是目标函数接受的参数类型数组。
首先, 代码使用 typeof 运算符判断传入的 fn 是否为一个函数, 如果不是, 则抛出一个错误, 提示参数错误。
接下来, 将 isAssignmentValid 标志设置为 true, 表示当前处于合法赋值状态。这是为了在注册函数时允许对 fnMap 进行赋值操作。
然后, 通过将参数数组 arr 使用 _ 连接起来, 生成一个用于标识参数类型的 key, 赋值给变量 typesKey 。
接着, 通过检查 fnMap 中是否存在以 fn.name 为键的属性, 来判断是否已经注册过该函数。如果不存在, 则创建一个新的 Proxy 对象, 并将其赋值给 fnMap 中以 fn.name 为键的属性。这个 Proxy 对象的作用是拦截对属性的赋值操作, 以控制赋值的合法性。
最后, 将函数 fn 存储到 fnMap[fn.name][typesKey] 的位置, 以完成函数的注册和存储。
这段代码的作用是在注册函数时, 将函数和对应的参数类型映射存储起来, 以便后续根据传入的参数类型选择正确的函数版本进行调用。
PART2:(注册重载实例同名函数)
这段代码的作用是在注册函数时, 为重载实例创建同名函数。
首先, 代码判断当前实例对象中是否已经存在同名函数 fn.name。如果不存在, 则进入条件语句块。
在条件语句块中, 代码定义了一个箭头函数, 并将其赋值给实例对象的同名属性 this[fn.name] 。这个箭头函数接受任意数量的参数 ...args。
在箭头函数内部, 首先调用 getParameterTypesKey 函数, 传入参数 ...args, 获取参数类型的 key, 并将其赋值给变量 typesKey。
接下来, 通过访问 fnMap 属性, 获取存储在 fnMap[fn.name][typesKey] 位置的目标函数, 并将其赋值给变量 targetFn。
最后, 箭头函数调用 targetFn, 并传入参数 ...args, 返回函数调用的结果。最后一行代码将 isAssignmentValid 标志设置为 false, 表示结束合法赋值状态。
这段代码的作用是为重载实例创建同名函数, 这些同名函数会根据传入的参数类型选择对应的函数版本进行调用。这样, 在调用重载函数时, 可以直接通过实例对象的同名属性来调用对应的函数版本。
步骤四
最后导出工厂函数, 提供业务侧使用。
基础使用
创建重载实例
const OverloadInstance = OverloadFactory() // 创建重载实例注册同名函数
OverloadInstance.reg(function exampleFn(param1, param2) { //注册同名函数
// 函数逻辑
}, ['number', 'string'])
OverloadInstance.reg(function exampleFn(param1) {
// 函数逻辑
}, ['number'])调用重载函数
OverloadInstance.exampleFn(20, 'hello world') // 调用重载函数
OverloadInstance.exampleFn(20)简单用例
let x = OverloadFactory() // 创建重载实例
x.reg(function name(a) { // 注册同名函数
console.log(a)
}, ['number'])
x.reg(function name(a, b) {
console.log(a + b)
}, ['number', 'number'])
x.reg(function text(a) {
console.log(a)
}, ['string'])
x.reg(function text(a, b) {
console.log(a + b)
}, ['string', 'number'])
x.name(20, 10) // 函数调用
x.name(20)
x.text('hello', 10)
x.text('hello')