前言
大家好,我是前端贰货道士。最近在整理vue
、js
和css
三件套的过程中,发现了一个好玩的东西,那就是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`
那么该如何实现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>
结语
大概就这样吧~