你知道如何实现js中的方法重载吗?

1,529 阅读2分钟

前言

  大家好,我是前端贰货道士。最近在整理vuejscss三件套的过程中,发现了一个好玩的东西,那就是js中的方法重载。国外系统的开发已接近尾声,本应该在整理面试不面试,你都必须得掌握的vue知识的我,因为没听过js中还有方法的重载。于是我停下整理vue知识的脚步,带着强烈的好奇心和求知欲深入研究,并整理出了这篇文章。

  这篇文章本应该出现在无论如何,你都必须得掌握的JS知识中,奈何由于篇幅过长,故新建一篇文章来记载。如果本篇文章对您有帮助,烦请大家一键三连哦, 蟹蟹大家~

什么是重载

  简单粗暴来说,就是对于一个相同的函数,通过使用不同个数的参数作为实参,来执行并处理不同逻辑的方法。理想很美好,但现实却很骨感。因为在js中,后定义的具有相同名称的方法会把前面定义的方法给覆盖掉,所以调用的方法会以最后一个为准,因此压根就不会实现方法的重载

function fn(name) {
  console.log(`我是${name}`)
}

function fn(name, age) {
  console.log(`我是${name},今年${age}岁`)
}

function fn(name, age, sport) {
  console.log(`我是${name},今年${age}岁,喜欢的运动是${sport}`))
}

`理想结果:`
fn('cxk')  `我是cxk`
fn('cxk', 18) `我是cxk,今年18岁`
fn('cxk', 18, '唱跳rap') `我是cxk,今年18岁,喜欢的运动是唱跳rap`

`现实:`
fn('cxk')  `我是cxk,今年undefined岁,喜欢的运动是undefined`
fn('cxk', 18) `我是cxk,今年18岁,喜欢的运动是undefined`
fn('cxk', 18, '唱跳rap') `我是cxk,今年18岁,喜欢的运动是唱跳rap`

       理想和现实.png

  那么该如何实现js的方法重载呢?

通过判断函数实参arguments的长度,来执行不同的方法

function fn() {
  switch (arguments.length) {
    case 1:
      var [name] = arguments
      `这里可以直接使用函数代替,如this.oneParamHandler(name)`
      `可以不用解构,直接使用展开运算符展开arguments,定义方法function oneParamHandler(name)`
      `使用this.oneParamHandler(...arguments)去执行`
      console.log(`我是${name}`)
      break;
      
    case 2:
      var [name, age] = arguments
       `这里可以直接使用函数代替,如this.twoParamsHandler(name, age)`
       `定义方法function twoParamsHandler(name, age)`
       `使用this.oneParamHandler(...arguments)去执行`
      console.log(`我是${name},今年${age}岁`)
      break;
      
    case 3:
      var [name, age, sport] = arguments
      `这里可以直接使用函数代替,如this.threeParamsHandler(name, age, sport)`
      `定义方法function threeParamsHandler(name, age, sport)`
      `使用this.oneParamHandler(...arguments)去执行`
      console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`)
      break;
  }
}

`实现效果:`

fn('cxk')  `我是cxk`
fn('cxk', 18) `我是cxk,今年18岁`
fn('cxk', 18, '唱跳rap') `我是cxk,今年18岁,喜欢的运动是唱跳rap`

通过作用域形成的原理和闭包的思想去递归

  这个通过作用域形成的原理和闭包的思想去递归解决js方法重载的灵感来源于John Resig

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<button>click1</button>
<button>click2</button>
<button>click3</button>
<script>

    let fn1 = null

    `addMethod方法相当于有两个函数,调用addMethod,相当于定义好addMethod中的第二个函数,再次()才是执行最终的函数`
    function addMethod(fn) {
        const old = fn1  `把前一次添加的方法存在一个临时变量old里面`
        return function () { 
            `此处fn的长度就是addMethod函数的实参个数, arguments的长度就是fn1执行时传入的实参个数`
            `如果传入的参数个数跟预期的一致,则直接调用`
            if (fn.length === arguments.length) {
                console.log('fn')
                return fn.apply(this, arguments)
            `否则,判断old是否是函数,如果是,就调用old`
            } else if (typeof old === "function") {
                console.log('old')
                return old.apply(this, arguments)
            }
        }

    }

   `
   特别注意:
    作用域链形成的时间点是编译的时候,而不是调用的时候。
    old在函数声明的时候就形成了一个作用域,在对应的fn1函数中,值永远不会改变
    核心思想是使用闭包的思想去递归
   `

    // 第一步
    fn1 = addMethod((name) => console.log(`我是${name}`))  `old: null`

    // 第二步
    fn1 = addMethod((name, age) => console.log(`我是${name},今年${age}岁`))  `old: 上个fn1`

    // 第三步
    fn1 = addMethod((name, age, sport) => console.log(`我是${name},今年${age}岁,喜欢的运动是${sport}`)) 
    `old: 上个fn1`
 
    `
    这三步走下来,fn1是有三个实参的addMethod方法,且不会再改变
    且执行结果与函数挂的载顺序无关,因为函数挂载完毕后,就形成了一条类似于原型链的函数链
    当找不到定义好的函数时,就会逐级向上找,直至找到为止
    `

    `
    点击第一个按钮,由于 3 !== 1, 且old是第二个fn1函数,则调用old方法,也就是第二个fn1函数
    结果发现 2 !== 1, 且old是第一个fn1函数,则调用old方法,也就是第一个fn1函数
    结果发现 1 === 1,就调用第一个fn1方法,执行并打印出我是fl
    `
    document.getElementsByTagName('button')[0].onclick = () => {
        fn1('cxk')   `打印顺序:old old fn`
    }

    `
    点击第二个按钮,由于 3 !== 2, 且old是第二个fn1函数,则调用old方法,也就是第二个fn1函数
    结果发现 2 === 2, 则调用第二个fn1函数,并打印我是蔡徐坤,今年18岁
    `
    document.getElementsByTagName('button')[1].onclick = () => {
        fn1('cxk', 18)  `打印顺序:old`
    }

    `点击第三个按钮,由于 3 === 3, 则调用第三个fn1方法,并打印我是蔡徐坤,今年18岁,喜欢的运动是唱跳rap `
    document.getElementsByTagName('button')[2].onclick = () => {
        fn1('cxk', 18, '唱跳rap')  `打印顺序:fn`
    }

</script>
</body>
</html>

函数重载的公共方法封装(基于作用域和闭包的思想)

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>

    <button>click1</button>
    <button>click2</button>
    <button>click3</button>

    <script>
        `js重载的公共方法封装, 参数为由不同重载方法组成的数组`
        function createOverloaded(fnList) {
            let curFn = null

            function addMethod(fn) {
                const old = curFn
                return function () {
                    if (fn.length === arguments.length) return fn.apply(this, arguments)
                    else if (typeof old === 'function') return old.apply(this, arguments)
                }
            }

            fnList.map(fn => {
                curFn = addMethod(fn)
            })
            return curFn
        }

        `调用公共方法,传入不同的重载函数`
        const fn1 = createOverloaded([
            (name) => console.log(`我是${name}`),
            (name, age) => console.log(`我是${name},今年${age}岁`),
            (name, age, sport) => console.log(`我是${name},今年${age}岁,喜欢的运动是${sport}`),
            `后面可以根据需要,继续往下添加重载方法`
        ])
        
        `fn1和fn2这两个函数相互独立,不会相互影响`
        const fn2 = createOverloaded([
            (name) => console.log(`我是${name}`),
            (name, age) => console.log(`我是${name},今年${age}岁`),
            (name, age, sport) => console.log(`我是${name},今年${age}岁,喜欢的运动是${sport}`),
        ])

        `点击时,传入实参,执行方法`
        document.getElementsByTagName('button')[0].onclick = () => {
            fn1('cxk')
        }

        document.getElementsByTagName('button')[1].onclick = () => {
            fn1('cxk', 18)
        }

        document.getElementsByTagName('button')[2].onclick = () => {
            fn1('cxk', 18, '唱跳rap')
        }
    </script>
</body>

</html>

结语

  大概就这样吧~