JS的函数重载

4,747 阅读5分钟

一、 什么是函数重载

  • 什么是函数重载 函数重载是一个同名函数完成不同的功能,编译系统在编译阶段通过函数参数个数、参数类型不同,函数的返回值来区分该调用哪一个函数,即实现的是静态的多态性。但是记住:不能仅仅通过函数返回值不同来实现函数重载。

  • 什么是重载函数 重载函数是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。

总结:函数名相同,函数的参数不同(包括参数个数和参数类型),根据参数的不同去执行不同的操作。

JavaScript 中没有真正意义上的函数重载。

  • 原因: 重载是面向对象语言里很重要的一个特性,JavaScript 中没有真正的重载,是模拟出来的(因为JavaScript 是基于对象的编程语言,不是纯面向对象的,它没有真正的多态:如继承、重载、重写),本质原因是声明创建同名函数JS中默认覆盖。
<button onclick="testFnName(666)">点击有参Click</button>
<button onclick="testFnName()">点击无参Click</button>

function testFnName() {
    console.log('无参')
}
function testFnName(name) {
    console.log('有参', name)
}

上述代码中,无论点击哪个<button>按钮,执行的函数一定是最后一个命名为testFnName的函数,因此只能通过其他方式模拟函数重载。

二、为什么要模拟函数重载

(既然没有,何必强求)

######1. 减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。 在强类型语言中(如Java),因为数据类型的限制,很多构造函数实现功能相同,接收的数据类型不同,如果没有函数重载,那么构造函数要为每一种数据类型分别命名。 但是JavaScript作为弱类型语言,不存在这种问题,函数可以任意接受参数,那这种情况下为啥要强行模拟一个所谓'函数重载'呢。其实一个粗糙的例子就可以很好的说明问题: 一个数组,调用一个函数,不传参返回原数组,一个参数是数字时返回对应下标内容,,两个参数(数字)时,返回对应数组下标截取,返回新数组。

正常情况下,针对不定量的参数对应不同的操作内容,我们需要写三个对应的函数,三个变量名,但其实是一套逻辑程序的三个功能。

######2. 将你从数据结构或格式类型中解放思维,关注函数的抽象概念和其提供的功能逻辑设计。 例子:Array.splice()

######3. 针对已存在的正在使用的公共函数的修改与扩展提供另类可行思路。(可借用的有意义思路) 例子:已存在工具函数(例1)并在项目内大量使用,现在需求要在此基础上修改第三个参数,在第三个参数为固定内容时,对数组进行统一格式化处理。 见底部 附1

三、JavaScript模拟函数重载的实现方式

最下方有最简单粗暴的实现方式,这里列出常用的是为了扩展思路。

  1. 利用arguments 对象实现,粗糙的栗子
    var arr = [1,3,4,6,7,7,7,45,3];
    Array.prototype.finds = function() {
       var len = arguments.length;
       var that = this;

    
        if (len === 0) {
           console.log(`${len}参数`)
           return that;
        } else if (len === 1) {
            console.log(`${len}参数`)
            return that[arguments[0]]
        } else if (len === 2) {
            console.log(`${len}参数`)
            return that.slice(arguments[0], arguments[1])
        }
  }

    console.log(arr.finds());// 0参数 [1, 3, 4, 6, 7, 7, 7, 45, 3]
    console.log(arr.finds(1)); // 1参数 3
    console.log(arr.finds(1,2)); // 2参数 [3]

很容易理解和实现,但缺点在于参数过多或需要分辨参数数据类型时,判断条件过于复杂,不利于维护和扩展

  1. 利用对象和闭包特性

粗糙的栗子

var objectTest = {
        arr: [1,3,4,6,7,7,7,45,3]
    }

    function addMethod(object, name, fn) {
        var oldObject = object[name];
        object[name] = function() {
            // fn.length 函数实际需要参数个数
            if (fn.length === arguments.length) return fn.apply(this, arguments)
            if (typeof oldObject === 'function') return oldObject.apply(this, arguments);
        }
    }

    // 给 objectTest 对象添加处理 没有参数 的方法
    addMethod(objectTest, "findTest", find0);

    // 给 objectTest 对象添加处理 一个参数 的方法
    addMethod(objectTest, "findTest", find1);

    // 给 objectTest 对象添加处理 两个参数 的方法
    addMethod(objectTest, "findTest", find2);

    function find0() {
        return this.arr;
    }
    function find1(idx) {
        return this.arr[idx];
    }
    function find2(st, ed) {
        return this.arr.slice(st, ed);
    }

   console.dir(objectTest.findTest) // 图1
   console.log(objectTest.findTest()) // (9) [1, 3, 4, 6, 7, 7, 7, 45, 3]
   console.log(objectTest.findTest(4)) // 7
   console.log(objectTest.findTest(1,4)) // (3) [3, 4, 6]
   console.log(arr.findTest(1,4,7)) // undefined

jQuery 之父 John Resig写的实例,核心思路是利用闭包,将不同的函数以相同的属性名层层嵌套在对象中,将对象与对应的方法名设计好后,利用arguments传入参数fn实际所需参数对比判断真正的执行函数。

图1 - findTest.png

优点是设计后的函数清晰,能够跳出数据本身,对设计函数本身入手,可读性高,缺点是构建复杂业务逻辑时整体代码过多,不利于阅读

  1. 可选参数实现

粗糙的栗子

var arr = [1,3,4,6,7,7,7,45,3];
function disposeArr(num1 = null, num2 = null) {

    this.num1 = num1;
    this.num2 = num2;

    if (this.num2 !== null) return arr.slice(this.num1, this.num2)
    if (this.num1 !== null) return arr[this.num1]
    if (!this.num1 && !this.num2) return arr
}

   console.log(disposeArr()) // (9) [1, 3, 4, 6, 7, 7, 7, 45, 3]
   console.log(disposeArr(4)) // 7
   console.log(disposeArr(1,4)) // (3) [3, 4, 6]
   console.log(disposeArr(1,4,7)) // (3) [3, 4, 6]

非常强,可选参数/默认参数还有更多妙用,不要再用arguments,就这样。

附1

function disposeArr(num1 = null, num2 = null, status = null) {

    this.num1 = num1;
    this.num2 = num2;
    this.status = status;

    if (typeof this.status === 'string' && status === 'add') return num1 + num2;
    if (this.num2 !== null) return arr.slice(this.num1, this.num2)
    if (this.num1 !== null) return arr[this.num1]
    if (!this.num1 && !this.num2) return arr

}

   console.log(disposeArr()) // (9) [1, 3, 4, 6, 7, 7, 7, 45, 3]
   console.log(disposeArr(4)) // 7
   console.log(disposeArr(1,4)) // (3) [3, 4, 6]
   console.log(disposeArr(21,42,'add')) // 63

在不影响任何公共方法的情况下,有效添加满足条件的函数/操作内容

PS: TS应用可选参数/默认参数以及静态数据类型检查可以更有效更灵活的完成类似功能,但与ES6相同的问题是请注意参数的传入顺序以及可选参数必须在默认参数的尾部