在js中实现函数重载
作者:Seiya
时间:2019年05月14日
最近在看《JavaScript高级程序设计》一书,书中基本概念一章第四节,提到 JavaScript 没有真正的重载。通过网上搜索,了解到一种巧妙的方法实现函数重载,于是有了本文。
什么是函数重载
函数名相同,函数的参数列表不同(包括参数个数和参数类型),根据参数的不同去执行不同的操作。
举个例子:
function doAdd(num){
console.log(num)
}
function doAdd(num1, num2){
console.log(num1 + num2)
}
// 在支持重载的编程语言中,比如 java
doAdd(10); // 10
doAdd(10, 20); // 30
// 在 JavaScript 中
doAdd(10); // NaN
doAdd(10, 20); // 30
这里,doAdd 函数被定义了两次,后定义的函数覆盖了先定义的函数。因此在只传入一个参数的情况下,第二个参数默认为 undefined,所以返回的结果为 NaN。
函数重载的好处
重载其实是把多个功能相近的函数合并为一个函数,重复利用了函数名,提高效率。比如如jQuery中的css( )方法不使用重载,那么就要有5个不同的函数,来完成功能。那我们就需要记住5个不同的函数名,和各个函数相对应的参数的个数和类型,显然就麻烦多了。
函数重载的实现方式
第一种方式:
function doAdd(num1, num2) { if(arguments.length == 1) { console.log(num1); } else if (arguments.length == 2) { console.log(num1 + num2); } } doAdd(10); // 10 doAdd(10, 20); // 30
这个例子是通过判断 arguments 对象的 length 属性来确定有几个参数,然后执行什么操作。在参数少的情况下,代码量不大,逻辑清晰。如果参数多一些,代码量会变得相当庞大,逻辑复杂,没有可读性。
- 第二种方式:
// addMethod - By John Resig (MIT Licensed) function addMethod(object, name, fn){ // 先把原来的object[name] 方法,保存在old中 var old = object[ name ]; // 重新定义 object[name] 方法 object[ name ] = function(){ // 如果函数需要的参数 和 实际传入的参数 的个数相同,就直接调用 fn if ( fn.length == arguments.length ) return fn.apply( this, arguments ); // 如果不相同,判断 old 是不是函数, // 如果是就调用 old,也就是刚才保存的 object[name] 方法 else if ( typeof old == 'function' ) return old.apply( this, arguments ); }; }
这是 jQuery 之父 John Resig 巧妙地利用了闭包,实现了JavaScript函数重载。
说明:
addMethod 函数,它接收3个参数
- 第一个:要绑定方法的对象,
- 第二个:绑定的方法名称,
- 第三个:需要绑定的方法
这个 addMethod 函数在判断参数个数的时候,除了用 arguments 对象,还用了函数的 length 属性。
函数的 length 属性,返回的是函数定义时形参的个数。
下面,我拿实际小程序项目中的需求来举例:
我们需要封装一个跳转页面的函数,当不传参数时跳转首页;传一个参数时,跳转对应地址;传两个参数时,根据传的第二个参数,使用不同的跳转方法。
P.S 这个例子只是用来举例,便于理解,实际项目中不需要通过重载来实现此需求。
function goIndex() {
wx.navigateTo({
url: '/pages/index'
})
}
function goPage1(url) {
wx.navigateTo({
url: url
})
}
function goPage2(url, type) {
wx.type({
url: url
})
}
下面来使用 addMethod 这个函数:
// 给 wx 对象添加处理 没有参数 的方法
addMethod(wx, "goPage", goIndex);
// 给 wx 对象添加处理 一个参数 的方法
addMethod(wx, "goPage", goPage1);
// 给 wx 对象添加处理 两个参数 的方法
addMethod(wx, "goPage", goPage2);
ddMethod 函数是利用了闭包的特性,通过 old 变量将先后绑定的函数链接起来,让所有的函数都留在内存中。每调用一次 addMethod 函数,就会产生一个 old,形成一个闭包。
这种方法有一些明显的缺陷:
重载只能处理输入参数个数不同的情况,它不能区分参数的类型、名称等其他要素。
重载过的函数将会有一些额外的负载,对于性能要求比较高的应用,使用这个方法要慎重考虑。
如果担心只绑定单个函数时的性能问题,你可以使用如下addMethod函数:
// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
var old = object[ name ];
if ( old )
object[ name ] = function(){
if ( fn.length == arguments.length )
return fn.apply( this, arguments );
else if ( typeof old == 'function' )
return old.apply( this, arguments );
};
else
object[ name ] = fn;
}
这样绑定第一个函数时,将不会有额外的操作,既简单又快速。当绑定更多函数时,则与原addMethod函数一样,会有额外的性能损失。
这样做还有一个额外的好处:对于那些参数个数不符合要求的函数调用,将统一又第一个绑定的函数处理。
总结
实际上,wx.goPage 只能绑定一个函数,那它为何可以处理3种不同的输入呢?它不可能同时绑定3个函数 goIndex, goPage1 与 goPage2 !这里的关键在于 old
属性。
由addMethod函数的调用顺序可知,wx.goPage 最终绑定的是 goPage2 函数。然而,在绑定 goPage2 时,old 为 goPage1 ;同理,绑定 goPage1 时,old为 goIndex 。3个函数 goIndex, goPage1 与 goPage2 就这样通过闭包链接起来了。
根据addMethod的逻辑,当fn.length与arguments.length不匹配时,就会去调用old,直到匹配为止。