您所浏览的网站已严重侵犯了原文作者nivk的权益。
本文由nivk撰写,摘抄自nivk的博客:http://blog.youkuaiyun.com/teajs
前言:为什么我们需要JavaScript重载函数?
一把剪刀可以用来做什么?
剪刀可以用来剪纸,也可以用来剪鱼。
现在,我们把剪刀剪东西这个动作封装成一个函数。
1、假设剪“纸”是一个String类型。
2、假设剪“肉”是一个Boolean类型。
现在,我们来实现一下。
function cut(obj) {
if (typeof obj === "string") {
console.log("我们在剪纸");
}
else if (typeof obj === "boolean") {
console.log("我们在剪肉");
}
}
没错,现在我们调用这个“cut”函数是可以满足我们的需要的。
甚至有人说,我可以做优化!比如这样:
function cut(obj) {
switch (obj) {
case "string":
console.log("我们在剪纸");
break;
case "boolean":
console.log("我们在剪肉");
break;
}
}
但是,这种程度的“优化”和本文是没有关系的。
我们需要考虑的是更复杂的情况,如果我需要一剪刀下去,同时剪了“纸”和“肉”,这时才能达到某个神秘成就。我们该如何做?
OK,我们先列举一下都有哪几种情况:
1、仅仅剪了“纸”。
2、仅仅剪了“肉”。
3、同时剪了“纸”和“肉”。
4、。。。。
等等!为什么有第四种情况?
嗯……你应该考虑一下,同时剪的时候,是先剪了“纸”,还是先剪了“肉”。
所以,我们重新列举一遍:
1、仅仅剪了“纸”。
2、仅仅剪了“肉”。
3、同时剪了“纸”和“肉”,并且先剪了“纸”。
4、同时剪了“纸”和“肉”,并且先剪了“肉”。
好的,我们用代码来实现一遍:
function cut(obj, obj1) {
if (typeof obj === "string" && typeof obj1 === "boolean"){
console.log("同时剪了纸和肉,并且先剪了纸");
}
else if (typeof obj === "boolean" && typeof obj1 === "string"){
console.log("同时剪了纸和肉,并且先剪了肉");
}
else if (typeof obj === "string") {
console.log("我们在剪纸");
}
else if (typeof obj === "boolean") {
console.log("我们在剪肉");
}
}
有同学有优化方案,但这同样不是我们要关注的地方。
重点是,这么多判断,你写着不累,我看着都累啊。
而且这仅仅是4种情况,真实项目环境情况可能比复杂的多。
构思:换一种可读性更好的方法来应对函数的“重载”。
如何使因参数变化而变化的函数内部实现更优雅?
要想实现方式优雅且更有实用性,有几个问题亟需解决:
1、去掉函数内部的类型判断。
2、加强函数参数的类型判断。
3、允许某个参数的泛类型。
4、允许限制某个参数的枚举类型。
关于以上4点的具体解释如下:
1、去掉函数内部的类型判断。
此条无需过多解释,所有优化点都围绕着这一点展开。
2、加强函数参数的类型判断。
可能有人觉得和第1条有所冲突,其实并不是。
第1条是将判断写在了函数内,而此条则是将其移到函数外去做。
3、允许某个参数的泛类型。
何为泛类型?因为JavaScript是弱类型语言,所以我们一直使用的都是泛类型。
但是既然我们要增加类型判断,也不能一棒子打死。
所以我们还是需要支持泛类型的。
4、允许限制某个参数的枚举类型。
这里我用“枚举类型”不是很合适,其实我想表达的意思是,某个参数可以是一个或多个限定范围的类型。
现在我提出一种用法:
var fn = Overload.create().
add("Number", function (num) {
console.log("数字:" + num);
}).
add("String", function (str) {
console.log("字符串:" + str);
}).
add("Number, String", function (num, str) {
console.log("数字:" + num + "字符串:" + str);
}).
add("String, Number", function (str, num) {
console.log("字符串:" + str + "数字:" + num);
}).
add("String || Number, Boolean", function (val, bool) {
console.log("字符串或数字:" + val + "布尔值:" + bool);
});
一眼望去是不是干净整洁了许多?而且在支持代码块折叠的编辑器中会如此显示:
非常容易找到你想了解的函数实现区域。
废话不多说,直接上代码!
!function () {
/* 私有变量 */
var any = "[object Unkonw]";
/* 私有方法 */
function getType(str) {
/// <summary>根据字符串获取Js可用于判断的类型</summary>
/// <param name="str" type="String"></param>
/// <returns type="any" />
if (!str || !(str = str.toString().trim())) {
// 容错,传入了空字符串
return null;
}
switch (str) {
case "Number": case "number":
case "String": case "string":
case "Boolean": case "boolean":
case "Function": case "function":
case "Object": case "object":
return str.toLowerCase();
case "Any": case "any": case "*":
return any;
case "Null": case "null": case "undefined":
throw new Error("Invalid type");
default:
return eval(str);
}
}
function processParameters(_parameters) {
/// <summary>
/// 参数类型处理
/// </summary>
var parameters;
var parameter;
for (var i = _parameters.length; i--;) {
// 遍历所有重载参数列表
parameters = _parameters[i];
if (parameters === null) {
// 跳过不需要参数的重载函数
continue;
}
for (var x = parameters.length; x--;) {
// 遍历某个重载的参数列表
parameter = parameters[x];
if (parameter instanceof Array) {
// 判断参数是否存在或者判断
for (var n = parameter.length; n--;) {
// 获取或者判断中每个参数的类型
parameter[n] = getType(parameter[n]);
}
} else {
// 不存在或者条件,直接获取参数类型
parameters[x] = getType(parameter);
}
}
}
}
/* 公开静态方法 */
window.Overload = {
create: function () {
/// <summary>创建重载对象</summary>
/// <returns type="Function" />
var _parameters = [];
var _functions = [];
var isProcessed = false;
function Overload() {
/// <summary>调用重载函数</summary>
if (_functions.length === 0) {
// 检查是否有可调用函数
throw new Error("Function not implemented");
}
if (!isProcessed) {
// 检查所有参数是否经过了类型处理
processParameters(_parameters);
isProcessed = true;
delete Overload.add;
}
var parameters;
for (var i = 0, len = _functions.length; i < len; i++) {
parameters = _parameters[i];
if (!parameters && !!arguments.length ||
!!parameters && arguments.length !== parameters.length) {
// 跳过参数数量不一致的重载(不包括参数列表为空的情况)
continue;
}
var checkDone = true;
if (parameters !== null) {
for (var x = 0, xLen = parameters.length; x < xLen; x++) {
// 遍历参数类型
var checkType;
var checkTypeof;
var argTypeof = typeof arguments[x];
if (parameters[x]._isOrParameters) {
// 检查并跳过或者判断参数类型不一致的重载
for (var n = 0, nLen = parameters[x].length; n < nLen; n++) {
checkType = parameters[x][n];
checkTypeof = typeof checkType;
if ((checkTypeof === "string" && argTypeof !== checkType ||
checkTypeof !== "string" && !(arguments[x] instanceof checkType)) &&
checkType !== any) {
if (n + 1 == nLen) {
// 找不到任何匹配的参数
checkDone = false;
break;
}
} else {
// 找到了匹配参数,直接进入下一步
break;
}
}
} else {
// 检查并跳过参数类型不一致的重载
checkType = parameters[x];
checkTypeof = typeof checkType;
if ((checkTypeof === "string" && argTypeof !== checkType ||
checkTypeof !== "string" && !(arguments[x] instanceof checkType)) &&
checkType !== any) {
checkDone = false;
break;
}
}
}
}
if (checkDone) {
return _functions[i].apply(this, arguments);
}
}
throw new Error("Invalid parameter");
};
Overload.add = function (str, fun) {
/// <summary>添加重载函数</summary>
/// <param name="str" type="String">参数列表字符串</param>
/// <param name="fun" type="Function">重载调用的函数</param>
/// <returns type="OverloadFunction" />
var parameters = null;
if (typeof str === "string") {
parameters = str.trim().split(",");
for (var i = parameters.length; i--;) {
if (parameters[i].indexOf("||") >= 1) {
parameters[i] = parameters[i].split("||");
parameters[i]._isOrParameters = true;
}
}
}
_parameters.push(parameters);
_functions.push(fun);
return Overload;
};
return Overload;
}
};
}();
若您有任何想要详细了解的地方可以在本文最下方评论留言,我会在看到您的留言后第一时间给您回复!