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

识别文本签名
我们先来回顾一下上一篇文章中提到的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项目的维护成本。

(0)

相关推荐

  • C++运算符重载 成员函数与友元函数详解

    复制代码 代码如下: #include<iostream>using namespace std;class A{    int x,y;    public:    A(int xx,int yy):x(xx),y(yy){}    A(){x=0;y=0;}    A operator+(const A&b) //不加const限定,也可以    { return A(x+b.x,y+b.y); }    A operator-()    { return A(-x,-y); } 

  • 为JavaScript添加重载函数的辅助方法

    JavaScript的重载函数,一般是靠对arguments判断来操作的. 比如: 复制代码 代码如下: var afunc = function() { args = arguments; if(args.length == 1) { console.log(1); }else if(args.length == 2) { console.log(2); }else if (args.length == 3) { console.log(3); } } 可以想象如果重载数量多的时候,要有多少的

  • 解析C++中不能重载为友元函数的四个运算符

    C++规定有四个运算符 =, ->, [], ()不可以是全局域中的重载(即不能重载为友员函数),这是为什么呢?现在先说说赋值运算符"="的重载C++规定赋值运算符"="只能重载为类的非静态成员函数,而不可以重载为类的友元函数.不能重载为类的静态成员应该比较容易理解,因为静态成员函数是属于整个类的,不是属于某个对象的,它只能去操作类静态数据成员.而赋值运算符"="是基于对象操作的.那么为什么赋值运算符不可以重载为类的友元函数?像同样都是双目

  • 添加JavaScript重载函数的辅助方法2

    代码依然简单.所以依然没什么好解释的.. 复制代码 代码如下: /** KOverLoad 一个创建重载函数的辅助方法. 补充上次的函数. @Author ake 2010-07-03 @weblog http://www.cnblogs.com/akecn */ var KOverLoad = function(scope) { this.scope = scope || window; //默认添加方法到这个对象中.同时添加的方法的this指向该对象. this.list = {}; //存

  • 详解C++中的函数调用和下标以及成员访问运算符的重载

    函数调用 使用括号调用的函数调用运算符是二元运算符. 语法 primary-expression ( expression-list ) 备注 在此上下文中,primary-expression 为第一个操作数,并且 expression-list(可能为参数的空列表)为第二个操作数.函数调用运算符用于需要大量参数的操作.这之所以有效,是因为 expression-list 是列表而非单一操作数.函数调用运算符必须是非静态成员函数. 函数调用运算符在重载时不会修改函数的调用方式:相反,它会在运算

  • C++函数重载详解及实例代码

    C++函数的重载 定义 在同一个作用域中,函数名相同,函数的参数列表不同的函数之间构成重载关系,在不同作用域中的同名函数遵循标识符隐藏的原则 ATTENTION:重载与函数的返回值类型无关,因为声明一个函数不需要返回类型,所以无法用来区分哪个函数 常函数和普通成员函数之间构成重载,重载时常对象调用常成员函数,一般对象调用一般成员函数 class A{ - public: void getVal()const{-} void getVal(){-} }; int main(){ const A a

  • Javascript基础 函数“重载” 详细介绍

    Javascript不像其他编程语言一样具有函数签名(什么是函数签名,简单的说就是说函数的接受参数类型和参数个数,也有人认为返回类型也应该包括.具体概念大家可以到网上查询). 所以Javascript是不能像其他语言一样实现方法名相同,参数个数不同-的这类重载的,不信你可以试试: 复制代码 代码如下: function show(){             alert("1");         }         function show(num1){             a

  • C++函数重载的深入解析

    我们在开瓶瓶罐罐的时候,经常会遭遇因各种瓶口规格不同而找不到合适的工具的尴尬.所以有时候就为了开个瓶,家里要备多种规格的开瓶器.同样是开个瓶子嘛,何必这么麻烦?于是有人发明了多功能开瓶器,不管啤酒瓶汽水瓶还是软木塞的红酒瓶都能轻松打开. 然而开瓶器的问题也会发生到程序设计中.比如我们要编写一个函数来求一个数的绝对值,然而整数.浮点型数.双精度型数都有绝对值,但为它们编写的函数返回值类型却是各不相同的.比如: 复制代码 代码如下: int iabs(int a);float fabs(float

  • 让JavaScript 轻松支持函数重载 (Part 1 - 设计)

    JavaScript支持重载吗? JavaScript支持函数重载吗?可以说不支持,也可以说支持.说不支持,是因为JavaScript不能好像其它原生支持函数重载的语言一样,直接写多个同名函数,让编译器来判断某个调用对应的是哪一个重载.说支持,是因为JavaScript函数对参数列表不作任何限制,可以在函数内部模拟对函数重载的支持. 实际上,在很多著名的开源库当中,我们都可以看到函数内部模拟重载支持的设计.例如说jQuery的jQuery.extend方法,就是通过参数类型判断出可选参数是否存在

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

    识别文本签名 我们先来回顾一下上一篇文章中提到的Overload用例: 复制代码 代码如下: var extend = Overload .add("*, ...", function(target) { }) .add("Boolean, *, ...", function(deep, target) { }); 我们允许用户输入一个字符串,表示某一个重载的签名.在用户调用函数时,我们需要拿着用户输入的参数实例去跟签名上的每一个参数类型作比较,因此我们需要先把这个

  • JavaScript中的函数重载深入理解

    在JavaScript中有一种特殊的数据类型---Function类型,JavaScript的每个函数都是Function类型的实例.由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定. <pre name="code" class="html">function sum(num1,num2) { return num1 +num2; } alert(sum(10,10)); //20 var other = sum; ale

  • 通过实例理解javascript中没有函数重载的概念

    将函数名想象为指针,也有助于理解为什么ECMAScript中没有函数重载的概念.如下例子: 复制代码 代码如下: function addSomeNum(num) {     return num+100; } function addSomeNum(num) {     return num+200; } var result=addSomeNum(100);//300 显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数.以上代码实际上与下面的代码是一致的. 复制代码 代

  • JS函数重载的解决方案

    在面向对象的编程中,很多语言都支持函数重载,能根据函数传递的不同个数.类型的参数来做不同的操作,JS对它却不支持,需要我们额外做些小动作. 在JS的函数执行上下文中有一个名为arguments的有意思的变量,它以数组的形式存储了函数执行时传递过来的所有参数,即使函数定义没有定义这 么多个形参.还有一个特别之处就是跟Array类型相比,arguments变量有且只有一个length属性,Array的方法,例如push.pop 等,它并不具备,它只是一个"伪数组":具有length属性,存

  • C++入门(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for)

    一.C++关键字 C++总共有63个关键字,在入门阶段我们只是大致了解一下就可,在后续博客中会逐渐讲解 二.命名空间 相信学过C++的同学,一定都写过下面这个简单的程序 #include<iostream> using namespace std; int main() { cout<<"hello world"<<endl; return 0; } 我们先来看第二行代码,using namespace std , 这行代码是什么意思呢 ? 这里我们

  • 如何利用饰器实现 Python 函数重载

    目录 装饰器实现Python 函数重载 一.为什么 Python 中没有函数重载? 二.在 Python 中实现函数重载 三.把函数封装起来 四.构建虚拟的命名空间 五.使用装饰器作为钩子 六.从命名空间中找到正确的函数 七.实现函数的调用 八.运用函数重载 九.总结 装饰器实现Python 函数重载 函数重载指的是有多个同名的函数,但是它们的签名或实现却不同.当调用一个重载函数 fn 时,程序会检验传递给函数的实参/形参,并据此而调用相应的实现. int area(int length, in

  • C++函数重载介绍与原理详解

    目录 函数重载 函数重载的概念 函数重载的原理(名字修饰) 总结: extern “C” 函数重载 函数重载的概念 函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表必须不同.函数重载常用来处理实现功能类似,而数据类型不同的问题. #include <iostream> using namespace std; int Add(int x, int y) { return x + y; } double Add(double x, doub

  • C++中函数重载详解

    目录 函数重载的概念 函数重载的应用 为什么C++支持函数重载,而C语言不支持 函数重载的概念 函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的 形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题. 函数重载的应用 1.比如以下代码,函数名一样,而参数的类型不同,在调用的时候编译器会根据传递的参数自动进行匹配. 2.在例如以下代码,我们进行编译,都可以编译成功. 3.接下来看一个有趣的现象,将上述第二个例子

  • C++入门语法之函数重载详解

    目录 写在前面 1 函数重载的概念 2 函数重载原理 总结 写在前面 关于C语言的编译与链接不懂的可以看一下下面的文章,先回顾一下以前的知识. 详解C语言的编译与链接 1 函数重载的概念 函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题. //1.函数的参数个数不同 #include <iostream> using namespace std; void

随机推荐