让 JavaScript 轻松支持函数重载 (Part 2 - 实现)


Posted in Javascript onAugust 04, 2009

识别文本签名
我们先来回顾一下上一篇文章中提到的Overload用例:

var extend = Overload 
.add("*, ...", 
function(target) { }) 
.add("Boolean, *, ...", 
function(deep, target) { });

我们允许用户输入一个字符串,表示某一个重载的签名。在用户调用函数时,我们需要拿着用户输入的参数实例去跟签名上的每一个参数类型作比较,因此我们需要先把这个字符串转换为类型数组。也就是说,字符串"Boolean, Number, Array"应该转换为数组[Boolean, Number, Array]。

在进行转换之前,我们先要考虑处理两个特殊类型,就是代表任意类型的"*",和代表任意数量的"..."。我们可以为它们定义两个专有的类型,以便在Overload内对它们做出特殊的兼容性处理:

Overload.Any = function() {}; 
Overload.More = function() {};

在有了这两个类型之后,字符串"Boolean, *, ..."就会被正确转换为数组[Boolean, Overload.Any, Overload.More]。由于Overload.Any和Overload.More都是函数,自然也都可以看做类型。

在这两个类型得到正确处理后,我们就可以开始编写识别文本签名的转换函数了:

if (signature.replace(/(^\s+|\s+$)/ig, "") == "") { 
signature = []; 
} else { 
signature = signature.split(","); 
for (var i = 0; i < signature.length; i++) { 
var typeExpression = signature[i].replace(/(^\s+|\s+$)/ig, ""); 
var type = null; 
if (typeExpression == "*") { 
type = Overload.Any; 
} else if (typeExpression == "...") { 
type = Overload.More; 
} else { 
type = eval("(" + typeExpression + ")"); 
} 
signature[i] = type; 
} 
}

我想这段代码相当容易理解,因此就不再解释了。我第一次写这段代码时忘记写上面的第一个if了,导致空白签名字符串""无法被正确识别为空白签名数组[],幸好我的unit test代码第一时间发现了这个缺陷。看来编写unit test代码还是十分重要的。

匹配函数签名
在我们得到函数签名的类型数组后,我们就可以用它和输入参数的实例数组做匹配了,以此找出正确的重载。在讨论具体如何匹配函数签名以前,我们先来看看C#或VB.NET这样的语言是如何处理函数重载匹配的。一般语言进行函数重载匹配的流程都是这样子的:

参数个数 - 参数个数不对的重载会被排除掉
参数类型 - 参数类型无法隐式转换为签名类型的会被排除掉
匹配个数 - 排除完毕后,剩下匹配的签名个数不同处理方法也不同
0个匹配 - 没有命中的匹配
1个匹配 - 这个就是命中的匹配
2个或以上的匹配 - 如果能在这些匹配中找出一个最佳匹配,那就命中最佳匹配;否则不命中任何匹配
在这一节里面,我们先处理流程中的前两个步骤,把参数个数或参数类型不一致的签名去掉:

var matchSignature = function(argumentsArray, signature) { 
if (argumentsArray.length < signature.length) { 
return false; 
} else if (argumentsArray.length > signature.length && !signature.more) { 
return false; 
} 
for (var i = 0; i < signature.length; i++) { 
if (!(signature[i] == Overload.Any 
|| argumentsArray[i] instanceof signature[i] 
|| argumentsArray[i].constructor == signature[i])) { 
return false; 
} 
} 
return true; 
};

为了作长度对比,我们需要在这个函数外对表示任何参数个数的"..."作一下特殊处理:
if (signature[signature.length - 1] == Overload.More) { 
signature.length = signature.length - 1; 
signature.more = true; 
}

这一段代码将会整合到第一节的转换函数末端,以便matchSignature函数能够轻易判断出参数与签名是否匹配。在最理想的情况下,我们对输入参数类型匹配到0个或1个重载,这样我们很容易就判断出命中哪个重载了。但如果有2个或以上的重载匹配,那么我们就要从中挑选一个最优的了,这正是下一节要讨论的内容。

处理多重匹配
关于C#是如何从多重匹配中选出较为匹配的重载,可以看C# Language Specification中的有关章节。我觉得通过三个简单的例子就能说明问题:

long Sum(int x, int y) { return x + y; } 
long Sum(long x, long y) { return x + y; } 
Sum(0, 1);

由于0和1这两个参数会被编译器理解为int类型,对于第1个重载它们都不用进行类型转换,都与第2个重载它们都要进行类型转换,因此第1个重载较优。

long Sum(int x, long y) { return x + y; } 
long Sum(long x, int y) { return x + y; } 
Sum(0, 1);

在第1个参数上,第1个重载较优;在第2个参数上,第2个重载较优。在这种情况下,任何一个重载都不优于另一个,找不到较优重载编译器就报错。

long Sum(int x, int y) { return x + y; } 
long Sum(int x, long y) { return x + y; } 
long Sum(long x, int y) { return x + y; } 
Sum(0, 1);

在第1个参数上,第1个重载优于第3个重载,于第2个重载无异;在第2个参数上,第1个重载优于第2个重载,于第3个重载无异。尽管第2个重载于第3个重载分不出个优劣来,但我们可以确定第1个重载比它们都要好,因此编译器选择了第1个重载。

假设我们有一个overloadComparator的比较函数,可以比较任意两个签名之间的优劣,我们需要对签名仅仅两两比较,以找出最优重载吗?事实上是不需要的,我们可以利用Array的sort方法,让它调用overloadComparator进行排序,排序后再验证前两名的关系就可以了——如果并列,则不命中任何一个;如果有先后之分,则命中第一个。

具体的overloadComparator代码就不在这里给出了,它依赖于另一个名为inheritanceComparator的比较函数来对比两个签名的参数类型哪一个更贴实际传入的参数类型,里面用到了一种比较巧妙的方法来判断两个类型是否为继承关系,以及是谁继承自谁。

小结
现在我们有了一个JavaScript的函数重载库,完整代码请看这里:函数重载库Overload。希望这个库能有效帮助大家提升JavaScript代码的可读性,降低大型Ajax项目的维护成本。

Javascript 相关文章推荐
索趣科技的答案
Feb 07 Javascript
在javascript中对于DOM的加强
Apr 11 Javascript
js 去除字符串第一位逗号的方法
Jun 07 Javascript
javascript制作坦克大战全纪录(1)
Nov 27 Javascript
jQuery-1.9.1源码分析系列(十)事件系统之事件包装
Nov 20 Javascript
JS实现的表格操作类详解(添加,删除,排序,上移,下移)
Dec 22 Javascript
javascript自动恢复文本框点击清除后的默认文本
Jan 12 Javascript
mac上node.js环境的安装测试
Jul 03 Javascript
在vue项目创建的后初始化首次使用stylus安装方法分享
Jan 25 Javascript
基于vue 实现表单中password输入的显示与隐藏功能
Jul 19 Javascript
JQuery省市联动效果实现过程详解
May 08 jQuery
Element MessageBox弹框的具体使用
Jul 27 Javascript
让JavaScript 轻松支持函数重载 (Part 1 - 设计)
Aug 04 #Javascript
JavaScript 异步调用框架 (Part 6 - 实例 &amp; 模式)
Aug 04 #Javascript
javascript 支持链式调用的异步调用框架Async.Operation
Aug 04 #Javascript
JavaScript 异步调用框架 (Part 5 - 链式实现)
Aug 04 #Javascript
JavaScript 异步调用框架 (Part 4 - 链式调用)
Aug 04 #Javascript
JavaScript 异步调用框架 (Part 3 - 代码实现)
Aug 04 #Javascript
JavaScript 异步调用框架 (Part 2 - 用例设计)
Aug 03 #Javascript
You might like
解析strtr函数的效率问题
2013/06/26 PHP
PHP实现采集中国天气网未来7天天气
2014/10/15 PHP
php文件操作小结(删除指定文件/获取文件夹下的文件名/读取文件夹下图片名)
2016/05/09 PHP
PHP实现将标点符号正则替换为空格的方法
2017/08/09 PHP
thinkphp5框架实现数据库读取的数据转换成json格式示例
2019/10/10 PHP
FireFox与IE 下js兼容触发click事件的代码
2008/11/20 Javascript
Jquery+ajax请求data显示在GridView上(asp.net)
2010/08/27 Javascript
基于jquery可配置循环左右滚动例子
2011/09/09 Javascript
在新窗口打开超链接的方法小结
2013/04/14 Javascript
如何使用jQuery来处理图片坏链具体实现步骤
2013/05/02 Javascript
javascript中RegExp保留小数点后几位数的方法分享
2013/08/13 Javascript
jQuery淡入淡出元素让其效果更为生动
2014/09/01 Javascript
Javascript学习之谈谈JS的全局变量跟局部变量(推荐)
2016/08/28 Javascript
对js中回调函数的一些看法
2016/08/29 Javascript
JavaScript类的写法
2016/09/17 Javascript
JS敏感词过滤代码
2016/12/23 Javascript
基于vue2的table分页组件实现方法
2017/03/20 Javascript
JS中关于正则的巧妙操作
2017/08/31 Javascript
详解开源的JavaScript插件化框架MinimaJS
2017/10/26 Javascript
使用vue的v-for生成table并给table加上序号的实例代码
2017/10/27 Javascript
vue+elementUI实现图片上传功能
2019/08/20 Javascript
快速了解Vue父子组件传值以及父调子方法、子调父方法
2020/07/15 Javascript
[01:25]2014DOTA2国际邀请赛 zhou分析LGD比赛情况
2014/07/14 DOTA
[01:13:59]LGD vs Mineski Supermajor 胜者组 BO3 第三场 6.5
2018/06/06 DOTA
python的常用模块之collections模块详解
2018/12/06 Python
pycharm设置鼠标悬停查看方法设置
2019/07/29 Python
在VS2017中用C#调用python脚本的实现
2019/07/31 Python
Django中F函数的使用示例代码详解
2020/07/06 Python
python中绕过反爬虫的方法总结
2020/11/25 Python
用CSS3将你的设计带入下个高度
2009/08/08 HTML / CSS
详解HTML5中的标签
2015/06/19 HTML / CSS
Dodax奥地利:音乐、电影、书籍、玩具、电子产品等
2019/08/31 全球购物
Yankee Candle官网:美国最畅销蜡烛品牌之一
2020/01/05 全球购物
校园报刊亭的创业计划书
2014/01/02 职场文书
2015年度服装销售工作总结
2015/03/31 职场文书
Python异常类型以及处理方法汇总
2021/06/05 Python