本文内容基本来自于JavaScript Ninja一书(79页),讲道理,我看懂那个方法都费了好大劲,也不知道理解的对不对,有大佬有好的想法可以供我参考参考会不胜感激~
javascript函数重载不同于其他语言的函数重载,它其实没有真正意义上的重载,只是通过传入的参数进行判断来模拟函数重载。
基本上有以下三种方法:
1、通用方法,根据传入参数的类型执行不同的操作。
2、通过某些特定参数是否存在来进行判断。
3、通过传入参数的个数进行判断。
接下来我们来看一下第三种方法
可能你会想到下面这种写法:
var ninja = {
whatever: function() {
switch(arguments.length) {
case 0:
/*do something*/
break;
case 1:
/*do something else*/
break;
case 2:
/*do yet something else*/
break;
//and so on ...
}
}
}
这种方式,通过arguments参数获取实际传入的参数个数进行判断,每一种情况都会执行不同的操作。但是这种判断方式看起来不是那么整洁。
假设出另外一种方式,如下思路:
var ninja = {};
addMethod(ninja, 'whatever', function() {/*do something*/});
addMethod(ninja, 'whatever', function() {/*do something else*/});
addMethod(ninja, 'whatever', function() {/*do yet something else*/});
先创建一个对象,然后使用同样的名称(whatever)将方法添加到该对象上,只不过每个重载的函数都是单独的。注意每个重载函数的参数个数都不相同。这样看起来整洁多了,接下来创建这个addMethod方法。
function addMethod(object, name, fn) {
var old = object[name]; //保存原有的函数,因为调用的时候可能不匹配传入的参数个数
object[name] = function() { //创建一个新匿名函数作为新方法
if(fn.length == arguments.length) { //如果该匿名函数的形参个数和实参个数匹配,就调用该函数
return fn.apply(this, arguments);
} else if(typeof old == 'function') { //如果传入的参数不匹配,则调用原有的函数
return old.apply(this, arguments);
}
}
}
addMethod()的第一次调用将创建一个新匿名函数,传入零个参数进行调用的时候将会调用该fn函数。由于此时ninja是一个新对象,所以这时候不用担心之前创建的方法。
第二次调用addMethod()的时候,首先将之前的同名函数保存到一个变量old中,然后将新创建的匿名函数作为方法。新方法首先检查传入的参数个数是否为1,如果是,就调用刚才传入的fn函数;如果不是,则重新调用存储在old上的函数,重新调用该函数时,将会再次检查参数个数是否为零,继而调用参数个数为零的fn版本的函数。
第三次调用addMethod()的时候,传入了一个接收两个参数的fn函数,然后判断逻辑相同:创建一个匿名函数作为方法,判断如果传入参数的个数为2个,则调用2个参数的fn函数,否则递归调用存储在old中的函数(此时old.length==1)。注:这个地方其实一直不太理解,在后面调用的时候是怎么个执行逻辑?
下面是测试代码:
var ninjas = { //创建一个基础对象,实现加载一些测试数据
values: ["Dean Edwards", "Sam Stephenson", "Alex Russell"]
}
addMethod(ninjas, "find", function () {
//在基础对象上绑定一个无参数方法
return this.values;
})
addMethod(ninjas, "find", function (name) {
//在基础对象上绑定一个单参数的方法
var ret = [];
for (var i = 0; i < this.values.length; i++) {
if (this.values[i].indexOf(name) == 0) {
ret.push(this.values[i])
}
}
return ret;
})
addMethod(ninjas, "find", function (first, last) {
//在基础对象上绑定两个参数的方法
var ret = [];
for (var i = 0; i < this.values.length; i++) {
if (this.values[i] == (first + " " + last)) {
ret.push(this.values[i]);
};
}
return ret;
})
console.log(ninjas.find().length); //3
console.log(ninjas.find("Sam").length); //1
console.log(ninjas.find("Dean", "Edwards").length); //1
console.log(ninjas.find("Aelx", "Russell", "Jr")); //undefined
我们声明并绑定的三个版本的find()方法。
1、第一个方法期待没有参数,并返回所有的ninjas;
2、第二个方法期望接收一个参数,并返回所有以传入文本开头的ninjas;
3、第三个方法期望接收两个参数,返回姓氏和名字都匹配所传入字符串参数的ninjas;
这些绑定函数实际上并没有存储于任何典型的数据结构中,而是在闭包里作为块引用进行存储。
需要注意的是:
1、重载只适用于不同数量的参数,但并不区分类型、参数名称或其他东西。这些才是我们经常想做的事情。
2、这样的重载方法会有一些函数调用的开销(递归调用),有高性能要求的时候需要考虑。