利用参数个数以及闭包进行函数重载

基于传入的参数,有很多种方法可以判断并进行函数重载。一种通用的方法是,根据传入参数的类型执行不同的操作;另一种方法是,可以通过判断某些特定的参数是否存在来执行不同的操作;还有一种方法是判断传入参数的个数来执行不同的操作。本文介绍的就是最后一种方法。

对于一个函数,在参数方面,我们可以确定两件事情:

  • 通过函数的length属性,可以知道声明了多少命名参数(形参个数)
  • 通过arguments.length,可以知道在调用时传入了多少参数(实参个数)

假设对象上有一个方法,通过判断实参个数来执行不同操作,如果是冗长且完整的函数,则会像下面的代码:

在这种方法中,通过arguments参数获取实参个数进行判断,每一种情况都会执行不同的操作。但这种判断方式很不简洁。

让我们假设别外一种方法,假如我想使用下面代码的语法来添加想要的重载方法该如何做呢?

在这里,先创建一个对象,然后使用相同的名称(whatever)将方法添加到该对象上,只不过每个重载的函数都是单独的。注意每个重载函数的参数个数都不相同。通过这种方式,我们真正可以为每个重载都创建一个独立的匿名函数,漂亮又简洁!

但是addMethod()函数目前还不存在,所以需要我们自己来创建它。瞪大你的眼睛!因为虽然段代码虽然有点短,但却很疯狂!

  • #1 保存原有的函数,因为调用的时候可能不匹配实参个数
  • #2 创建一个匿名作为新方法
  • #3 如果该匿名函数的形参个数和实参个数匹配,就调用该函数
  • #4 如果传入参数不匹配,则调用原有的函数

addMethod()函数接收了3个参数。

  • 要绑定方法的对象
  • 绑定方法所用的属性名称
  • 要绑定的方法

让我们回到刚才那个漂亮又简洁的语法中,解释下函数的具体形为:

第一次调用addMethod()将创建一个匿名函数,传入零个参数进行调用的时候将会调用该func函数。由于此时funcCollection 是一个新对象,所以这时候不用担心之前创建的方法。

第二次调用addMethod()的时候,首先将之前的同名函数保存到一个变量old#1)中,然后将新创建的匿名函数作为方法(#2)。新方法首先检查实参个数是否为1个,如果是,就调用刚才传入的func(a){...}函数;如果不是,则重新调用存储在old(#4)上的函数,重新调用该函数时,将会再次检查实参个数是否为零个,继而调用参数个数为零的func(){...}函数。

第三次调用addMethod()的时候,传入了一个接收两个参数的func(a,b){...}函数,然后判断逻辑相同:创建一个匿名函数作为方法,判断如果实参数量为2个,则调用2个参数的func(a,b){...}函数,并推迟之前创建的一个参数的函数。

这种方式就像剥洋葱皮一样,每一层都检查参数个数是否匹配如果不匹配的话,就推迟上一层创建的函数。

那内部匿名函数是如何访问到oldfunc的呢?三个形参个数不同的同名函数是如何存储的呢?这里使用的闭包。这些同名函数都是在闭包里作为引用进行存储(保存于作用域链中),所以不需要借助于任何典型的数据结构。

我想你现在应该已经晕乎了,那就看看下面的图解,三次调用过程中,闭包是如何形成的,以及起了什么作用:
invoke-flow

结合《JavaScript权威指南》中对闭包与作用域链的解释(P184):每次调用JavaScript函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。但是如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收。

addMethod()函数中,我们使用old变量指向引用上一次的funcCollection.func函数,通过这种方式将三个形参个数不同的同名函数留在了内存中。

让我们测试一下函数重载的功能:

运行结果:
overloading-test

注意:在使用这个特定的技巧时,需要注意以下几点:

  • 这种重载方式只适用于不同数量的参数,但并不区分类型、参数名称或其他东西。这些才是我们经常想做的事情。
  • 这样的重载方法会有一些函数调用的开销。我们要考虑在高性能时的情况

尽管如此,对于一些函数式技术,该函数提供了一个很好的例子,同时也介绍了函数的length属性。

发表评论