js的函数表达式之重载

1. 重载的概念

重载,简单说,就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。这样做的好处是减少了函数或方法名的数量,避免了名字空间的污染。

2. 没有重载

ECMAScript函数不能像传统意义上那样实现重载。而其他语言(如Java)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。而ECMAScript函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的

1
2
3
4
5
6
7
8
9
function add(num) {
return num + 1;
}

function add(num) {
return num + 2;
}
var rst = add(1);
console.log(rst);//3

如果在ECMAScript中定义了两个名字相同的函数,则该名字只属于后定义的函数。

3. 模仿重载

通过检测传入函数中的类型和数量并做出不同的反应,可以模仿方法的重载。

3.1 简单的思路

根据arguments.length值的不同执行不同的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function f1(a,b...) {
switch(arguments.length) {
case 0:
//操作1的代码
break;
case 1:
//操作2的代码
break;
case 2:
//操作3的代码

//...
}
}

3.2 利用闭包

出处JQuery之父John Resig的《secrets of Javascript ninja》

加入有这样一个需求,有一个people对象,里面存着一些人名

1
2
3
var people = {
values: ["张 三", "李 四", "王 五", "赵六",];
};

我希望对people实现一个find方法。

不传任何参数时,就把people.values里面的所有元素返回来;
当传一个参数时,就把 符合的姓 返回来;
当传两个参数时,就把 符合的姓名 都返回来;

这个find方法是根据参数的个数不同而执行不同的操作的(也就是需要实现重载),所以希望有一个addMethod方法,能够为people添加find重载

1
2
3
addMethod(people, "find", function() {});
addMethod(people, "find", function(a) {});
addMethod(people, "find", function(a,b) {});

实现addMethod方法

1
2
3
4
5
6
7
8
9
10
11
12
function addMethod(object, name, fn) {
var old = object[name];///把前一次添加的方法存在一个临时变量old里面
object[name] = function() {
//如果调用object[name]方法时,传入的参数个数跟预期的一直,则直接调用
if(fn.length === arguments.length) {//传入的形参和实参一样
return fn.apply(this, arguments);
//否则,判断old是否是函数,如果是,就调用
}else if(typeof old === "function") {
return old.apply(this, arguments);
}
}
}

下面这个addMethod能稍微改善只绑定单个函数时的性能问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function addMethod(object, name, fn) {
var old = object[name];///把前一次添加的方法存在一个临时变量old里面
if(old) {
object[name] = function() {
//如果调用object[name]方法时,传入的参数个数跟预期的一直,则直接调用
if(fn.length === arguments.length) {//传入的形参和实参一样
return fn.apply(this, arguments);
//否则,判断old是否是函数,如果是,就调用
}else if(typeof old === "function") {
return old.apply(this, arguments);
}
}
}else {
object[name] = fn;
}

}

使用addMethod的方法
第一种,你可以将find函数直接添加到每个对象实例:

1
2
3
4
5
6
7
8
9
10
11
function People() {
addMethod(this, "find", function() {
//执行代码
});
addMethod(this, "find", function(firstName) {
//执行代码
});
addMethod(this, "find", function(firstName, lastName) {
//执行代码
});
}

第二种,你可以将find函数添加到对象的prototype,这样所有对象实例将共享find函数

1
2
3
4
5
6
7
8
9
10
11
function People() {
addMethod(People.prototype, "find", function() {
//执行代码
});
addMethod(People.prototype, "find", function(firstName) {
//执行代码
});
addMethod(People.prototype, "find", function(firstName, lastName) {
//执行代码
});
}

全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
function addMethod(object, name, fn) {
var old = object[name];

object[name] = function() {
//如果调用object[name]方法时,传入的参数个数跟预期的一直,则直接调用
if(fn.length === arguments.length) {//传入的形参和实参一样
return fn.apply(this, arguments);
}else if(typeof old === "function") {
return old.apply(this, arguments);
}
}
}
//--
function find0() {
return this.values;
}
function find1(firstName) {
var rst = [];
for(var i = 0; i < this.values.length; i++) {
if(this.values[i].indexOf(firstName) === 0) {
rst.push(this.values[i]);
}
}
return rst;
}
function find2(firstName, lastName) {
var rst = [];
for(var i = 0; i < this.values.length; i++) {
if(this.values[i] === (firstName + " " + lastName)) {
rst.push(this.values[i]);
}
}
return rst;
}
//--
function People() {
addMethod(this, "find", find0);
addMethod(this, "find", find1);
addMethod(this, "find", find2);
}

var myPeople = new People();
myPeople.values = ["张 三", "李 四", "王 五", "赵 六"];

console.log(myPeople.find());
console.log(myPeople.find("张"));
console.log(myPeople.find("李", "四"));

3.2.1 缺陷

  • 3.2的重载只能处理输入参数个数不同的情况,它不能区分参数的类型、名称等其他要素。
  • 重载过的函数将会有一些额外的负载,对于性能有要求比较高的应用,使用这个方法要慎重。

3.3 整理

这个程序的难点在于myPeople.find事实上只能绑定一个函数。之所以能够处理3中不同的输入的关键在于old属性

由addMethod函数的调用顺序可知,myPeople.find最终绑定的时find2函数。然而在绑定find2时,old为find1;同理,绑定find1时,old为find0。3个函数fond0,find1,find2就专业那个通过闭包连接起来了。

根据addMethod的逻辑,当fn.length和arguments.length不匹配时,就会去调用old,直到匹配为止。