一、 什么是函数重载
-
什么是函数重载 函数重载是一个同名函数完成不同的功能,编译系统在编译阶段通过函数参数个数、参数类型不同,函数的返回值来区分该调用哪一个函数,即实现的是静态的多态性。但是记住:不能仅仅通过函数返回值不同来实现函数重载。
-
什么是重载函数 重载函数是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。
总结:函数名相同,函数的参数不同(包括参数个数和参数类型),根据参数的不同去执行不同的操作。
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模拟函数重载的实现方式
最下方有最简单粗暴的实现方式,这里列出常用的是为了扩展思路。
- 利用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]
很容易理解和实现,但缺点在于参数过多或需要分辨参数数据类型时,判断条件过于复杂,不利于维护和扩展
- 利用对象和闭包特性
粗糙的栗子
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实际所需参数
对比判断真正的执行函数。
优点是设计后的函数清晰,能够跳出数据本身,对设计函数本身入手,可读性高,缺点是构建复杂业务逻辑时整体代码过多,不利于阅读
- 可选参数实现
粗糙的栗子
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
相同的问题是请注意参数的传入顺序
以及可选参数必须在默认参数的尾部
。