在之前一篇关于函数参数的文章中说到,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'
}]
}
下面为这个群体添加一个查找方法,要求:
- 当不传入参数时,返回这个群体中的所有人及总人数
- 当传入一个参数时,如果这个参数能转化为 true 则返回群体中的所有男性及男性人数,否则返回所有女性及女性人数
- 当传入两个参数时(第二个参数为数字类型),如果第一个参数能转化为 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)
}
}
}
上面代码中的方法是用来为一个对象添加自定义方法的方法,接收三个参数:
- 需要添加自定义方法的对象
- 自定义方法名
- 自定义方法的函数体
使用方式如下:
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.length
与arguments.length
的长度是否相同,如果相同的话,就执行当前闭包持有的fn
;如果不相同并且当前闭包持有的temp
为函数,则执行temp
。可以发现,一共执行了三次
addMethodToObject
,除了第一次的temp
是undefined
以外,后面两次持有的temp
都是上一次执行时生成的闭包。
而在我们调用自定义方法时,实际的执行逻辑和递归调用就有点类似了。感兴趣的小伙伴可以自己研究下到底是怎样的执行逻辑。
扫码关注微信公众号【前端程序员的斜杠青年进化录】 微信扫码,给我赞赏一下~最后还要说一点:在上面的 demo 中,请不要使用 ES6 的箭头函数。由于箭头函数没有绑定自己的
this
arguments
等,所以如果改写为箭头函数会报错。
网友评论