美文网首页前端进阶之路让前端飞
JavaScript 函数重载的实现思路

JavaScript 函数重载的实现思路

作者: ac68882199a1 | 来源:发表于2018-03-04 19:37 被阅读47次

    在之前一篇关于函数参数的文章中说到,javaScript 本身是没有函数重载这个概念的,出现同名函数会直接覆盖,不了解重载意义的小伙伴可以先看一下上一篇文章: JavaScript基础夯实——函数的参数

    虽然本身不支持重载,但是由于可变参数的特性,我们就可以间接地实现函数的重载。

    下面节介绍两种实现重载的思路:

    参数个数实现方法

    参数个数的方法主要是通过 arguments 关键字来实现的,比如:

    arguments 的长度为 0 时,执行逻辑 a,为 1 时执行逻辑 b,为 2 时执行逻辑 c ……

    function overrideByArgsLength (arg1, arg2, arg3) {
      const length = arguments.length
      if (length === 0) {
        console.log('logic a with no argument')
      } else if (length === 1) { // arg1 is exists
        console.log('logic b with argument arg1', arg1)
      } else if (length === 2) { // arg1 arg2 are exists
        console.log('logic c with arguments arg1 & arg2', arg1, arg2)
      } else { // all arguments are exists
        console.log('logic d with arguments arg1 & arg2 & arg3', arg1, arg2, arg3)
      }
    }
    

    你也可以在声明函数时不写参数,在函数体中直接通过下标的方式 arguments[0] arguments[1] 来调用参数。但是这样并不利于函数的可读性。

    函数体中的判断语句也可以改写为 switch 语句

    上面这种方式是 js 实现函数重载的一种非常简单的方式,来举个实际的例子看看:

    某个群体有 10 个人,其中男性 6 人,女性 4 人,年龄分布在各个阶段:

    const group = {
        total: 10,
        male: 6,
        female: 4,
        peoples: [{
          age: 12,
          sex: 'male'
        }, {
          age: 18,
          sex: 'female'
        }, {
          age: 22,
          sex: 'male'
        }, {
          age: 34,
          sex: 'male'
        }, {
          age: 60,
          sex: 'male'
        }, {
          age: 73,
          sex: 'female'
        }, {
          age: 6,
          sex: 'female'
        }, {
          age: 47,
          sex: 'male'
        }, {
          age: 3,
          sex: 'male'
        }, {
          age: 24,
          sex: 'female'
        }]
    }
    

    下面为这个群体添加一个查找方法,要求:

    1. 当不传入参数时,返回这个群体中的所有人及总人数
    2. 当传入一个参数时,如果这个参数能转化为 true 则返回群体中的所有男性及男性人数,否则返回所有女性及女性人数
    3. 当传入两个参数时(第二个参数为数字类型),如果第一个参数能转化为 true,则返回所有年龄大于等于第二个参数的人及人数,否则返回所有年龄小于第二个参数的人及人数

    下面是通过判断语句简单实现的查找函数重载(无参数类型校验):

    group.find = function (condition, age) {
        const length = arguments.length
    
      switch (length) {
        case 0:
          return {
            peoples: this.peoples,
            count: this.peoples.length
          }
    
        case 1:
          if (!!condition) {
            const male = this.peoples.filter(item => item.sex === 'male')
            return {
              peoples: male,
              count: male.length
            }
          } else {
            const female = this.peoples.filter(item => item.sex === 'female')
            return {
              peoples: female,
              count: female.length
            }
          }
    
        case 2:
          if (!!condition) {
            const elder = this.peoples.filter(item => item.age >= age)
            return {
              peoples: elder,
              count: elder.length
            }
          } else {
            const younger = this.peoples.filter(item => item.age < age)
            return {
              peoples: younger,
              count: younger.length
            }
          }
      }
    }
    
    group.find()
    group.find(1)
    group.find(0, 40)
    

    这一大坨代码看这着实有些恶心,所有的逻辑都写在一个函数中,完全不利于代码的维护和复用,而当我们需要为不同的对象添加自定义方法时,总不能每添加一次就 switch 一遍吧?那有没有更高效便于复用的方式呢?

    其实,通过闭包的方式,我们就可以相对方便地实现。

    闭包方式实现方法

    废话不多说,直接上代码:

    function addMethodToObject (obj, name, fn) {
      const temp = obj[name]
      obj[name] = function () {
        if (fn.length === arguments.length) {
          return fn.apply(obj, arguments)
        } else if (typeof temp === 'function') {
          return temp.apply(obj, arguments)
        }
      }
    }
    

    上面代码中的方法是用来为一个对象添加自定义方法的方法,接收三个参数:

    1. 需要添加自定义方法的对象
    2. 自定义方法名
    3. 自定义方法的函数体

    使用方式如下:

    addMethodToObject(group, 'fetch', function () {
      return {
        peoples: this.peoples,
        count: this.peoples.length
      }
    })
    
    addMethodToObject(group, 'fetch', function (isMale) {
      if (!!isMale) {
        const male = this.peoples.filter(item => item.sex === 'male')
        return {
          peoples: male,
          count: male.length
        }
      } else {
        const female = this.peoples.filter(item => item.sex === 'female')
        return {
          peoples: female,
          count: female.length
        }
      }
    })
    
    addMethodToObject(group, 'fetch', function (elder, age) {
      if (!!elder) {
        const elder = this.peoples.filter(item => item.age >= age)
        return {
          peoples: elder,
          count: elder.length
        }
      } else {
        const younger = this.peoples.filter(item => item.age < age)
        return {
          peoples: younger,
          count: younger.length
        }
      }
    })
    
    group.fetch()
    group.fetch(1)
    group.fetch(0, 40)
    

    下面说一下代码的执行逻辑:

    每次执行 addMethodToObject 的时候,内部变量 temp 会持有 group 对象上的 fetch 属性,然后为 fetch 重新定义一个函数(闭包),由于闭包的特性,这个函数会持续持有 temp 变量和参数 fn

    在闭包中,通过比较 fn.lengtharguments.length 的长度是否相同,如果相同的话,就执行当前闭包持有的 fn;如果不相同并且当前闭包持有的 temp 为函数,则执行 temp

    可以发现,一共执行了三次 addMethodToObject,除了第一次的 tempundefined 以外,后面两次持有的 temp 都是上一次执行时生成的闭包。

    而在我们调用自定义方法时,实际的执行逻辑和递归调用就有点类似了。感兴趣的小伙伴可以自己研究下到底是怎样的执行逻辑。

    最后还要说一点:在上面的 demo 中,请不要使用 ES6 的箭头函数。由于箭头函数没有绑定自己的 this arguments 等,所以如果改写为箭头函数会报错。

    扫码关注微信公众号【前端程序员的斜杠青年进化录】 微信扫码,给我赞赏一下~

    相关文章

      网友评论

        本文标题:JavaScript 函数重载的实现思路

        本文链接:https://www.haomeiwen.com/subject/vmfqfftx.html