让 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 相关文章推荐
jquery 指南/入门基础
Nov 30 Javascript
该如何加载google-analytics(或其他第三方)的JS
May 13 Javascript
ASP 过滤数组重复数据函数(加强版)
May 31 Javascript
给jqGrid数据行添加修改和删除操作链接(之一)
Nov 04 Javascript
jQuery 遍历- 关于closest() 的方法介绍以及与parents()的方法区别分析
Apr 26 Javascript
利用javascript实现web页面中指定区域打印
Oct 30 Javascript
jQuery的cookie插件实现保存用户登陆信息
Apr 15 Javascript
jQuery实现仿路边灯箱广告图片轮播效果
Apr 15 Javascript
jQuery 判断是否包含在数组中Array[]的方法
Aug 03 Javascript
微信小程序 安全包括(框架、功能模块、账户使用)详解
Jan 16 Javascript
React Native 自定义下拉刷新上拉加载的列表的示例
Mar 01 Javascript
VUE中使用MUI方法
Feb 12 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
php escape URL编码
2008/12/10 PHP
php使用curl和正则表达式抓取网页数据示例
2014/04/13 PHP
thinkphp普通查询与表达式查询实例分析
2014/11/24 PHP
php操作memcache缓存方法分享
2015/06/03 PHP
从刷票了解获得客户端IP的方法
2015/09/21 PHP
javascript编程起步(第四课)
2007/02/27 Javascript
js 事件小结 表格区别
2007/08/13 Javascript
jQuery Tools tooltip使用说明
2012/07/14 Javascript
Firefox中通过JavaScript复制数据到剪贴板(Copy to Clipboard 跨浏览器版)
2013/11/22 Javascript
绑定回车enter事件代码
2014/05/18 Javascript
JavaScript+CSS无限极分类效果完整实现方法
2015/12/22 Javascript
使用bootstrap typeahead插件实现输入框自动补全之问题及解决办法
2016/07/07 Javascript
深入理解(function(){... })();
2016/08/16 Javascript
每个程序员都需要学习 JavaScript 的7个理由小结
2016/09/03 Javascript
jQuery内容过滤选择器用法示例
2016/09/09 Javascript
JavaScript获取短信验证码(周期性)
2016/12/29 Javascript
利用Plupload.js解决大文件上传问题, 带进度条和背景遮罩层
2017/03/15 Javascript
Angular实现的内置过滤器orderBy排序与模糊查询功能示例
2017/12/29 Javascript
React学习笔记之高阶组件应用
2018/06/02 Javascript
Vuex 快速入门(简单易懂)
2018/09/20 Javascript
angularJs自定义过滤器实现手机号信息隐藏的方法
2018/10/08 Javascript
给localStorage设置一个过期时间的方法分享
2018/11/06 Javascript
js+HTML5 canvas 实现简单的加载条(进度条)功能示例
2019/07/16 Javascript
微信小程序授权登陆及每次检查是否授权实例代码
2019/09/18 Javascript
jQuery高级编程之js对象、json与ajax用法实例分析
2019/11/01 jQuery
Vue实现星级评价效果实例详解
2019/12/30 Javascript
python OpenCV学习笔记之绘制直方图的方法
2018/02/08 Python
Python使用numpy实现BP神经网络
2018/03/10 Python
python 实现 hive中类似 lateral view explode的功能示例
2020/05/18 Python
Python如何爬取qq音乐歌词到本地
2020/06/01 Python
iostream与iostream.h的区别
2015/01/16 面试题
新闻学专业应届生求职信
2013/11/08 职场文书
2015年中个人总结范文
2015/03/10 职场文书
早会开场白台词大全
2015/06/01 职场文书
《惊弓之鸟》教学反思
2016/02/20 职场文书
2019年入党思想汇报格式与要求
2019/06/25 职场文书