在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,直到匹配为止。

最后更新时间: 8/8/2019, 3:30:39 PM