持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 2 天
函数重载(over load)
- 重载的本质是同名函数
在 Java 里面有一个特性,一个类的方法是可以同名的,只要 参数的类型/参数的个数 不同,就可以。
同名函数参数个数不同
// 伪代码
class X {
method(n: number){
return n + 1
}
method(n: string){
return parseInt(n) + 1
}
}
// 重载是为了解决 Java 的一个问题(问题:一个函数接受两个不同类型的参数)
JS 如何实现 一个函数接受不同类型的参数
// JQuery
$('#id') => 得到封装好的 $div 可以直接 $div.on
$(window) => $window 可以直接 $window.on
// 这个就是重载
if(type a === 'string'){}
else(tyep a === window){}
// 完全使用 if...else 做判断的
所以 JS 不需要重载,它用 if..else 就可以搞定,还可以根据 arguments.length 搞定。
现在的问题是 TS 是有类型的,所以 TS 就要去关注,如果一个函数接受不同类型的参数,我们要怎么去处理 ?
我们分三种情况讨论:
- 参数类型不同
- 参数个数不同
- 都不同
// 参数类型不同 使用联合类型
function print(x: string | number | boolean){
// 这里面就走 JS 的老路 使用 if...else 搞定
}
// 是不是什么类型 直接用 | 联合起来就可以了, 就不需要写两次了,Java 写两次是因为不支持这种写法
// 使用 重载: 同名不同参
function createDate(n: number): Date;
function createDate(year: number, month: number, date: number): Date;
function createDate(a: number, b?: number, c?: number): Date {
if (a !== undefined && b !== undefined && c !== undefined) {
return new Date(a, b, c)
} else if (a !== undefined && b === undefined && c === undefined) {
return new Date(a)
} else {
// 这里写是因为有可能别人是写我们打包好的 JS 的 createDate,这么写以防止意外
throw new Error('只接受一个或三个参数!')
}
}
createDate(100000)
createDate(2020, 1, 20)
createDate(2020, 1)
重载
重载来源于 Java/C# 这种面向对象的不支持联合类型的语言。
// 以下代码不使用重载实现,采用不同明函数
function createDateFromNumber(n: number): Date {
return new Date(n)
}
function createDateFromYMD(year: number, month: number, date: number): Date {
return new Date(year, month, date)
}
createDateFromNumber(100000)
createDateFromYMD(2020, 1, 20)
那么什么情况不用重载呢? 你写的东西, 根据你想把复杂度留给自己就用重载(库的提供者); 想把选择权交给使用者的时候就使用不同名函数(库的调用者)。
我对于重载的理解它的本质是复杂度守恒,我选择把复杂度留给自己,还是抛给对方。
指定函数的this
指定 this 的类型
- obj.fn()
- fn.call()
- fn.apply()
- fn.bind()
type Person = {
name: string
}
function f(this: Person, word: string) {
console.log(this.name + ' ' + word)
}
// 拼凑 person.f()
const p: Person & { f?: typeof f } = {name: 'hone'}
p.f = f
// 以上代码另一种实现
type Person = {
name: string,
sayHi: (this: Person, word: string) => void
}
function f(this: Person, word: string) {
console.log(this.name + ' ' + word)
}
const p: Person = {name: 'hone', sayHi: f}
p.sayHi('hi')
type Person = {
name: string
}
function f(this: Person, word: string) {
console.log(this.name + ' ' + word)
}
// f.call(this, p1)
f.call(p, 'hi')
// f.apply(this, [p1])
f.apply(p, ['hi'])
// f2 = f.bind(this, p1, p2, p3); f2()
// 可以看出 bind 其实就是对柯里化的应用
f.bind(p)('hi')
// 还可以这么写一下
f.bind(p).bind(null, 'hi')
剩余参数
function sum(name: string, ...ary: number[]) {
return ary.reduce((result, num) => result + num, 0)
}
sum('one', 1)
sum('two', 1, 2)
sum('six', 1, 2, 3, 4, 5, 6)
展开参数
function sum(name: number, ...ary: number[]) {
// f(array) 如何把 array 传给 f
// 第一种写法:f.apply(null, ary)
// 第二种写法:
f(...ary) // 展开就是把这个数组的每一项都写在这个() 里面
}
function f(...ary: number[]) {
console.log(ary)
}
as const 是什么
当我们写 const a = 'b' 那么 TS 就会自动的进行类型推导 a 的类型是 string。
根据逻辑const a 是一个只读的常量,那么我们知道常量的话,我现在把它声明 'b' 我以后还有可能把它变成 c 吗? 他不可能变成 c 的,所以 const 进行类型推导的时候往小了推。
那如果是let a = 'b',既然是一个变量那就有可能 a = 'c' a = 'd', 我并不知道你会变成什么,但是它至少很有可能会变的,所以该如何推理类型呢?那就只能往大了推,那么就是 string。
// 你就把 'b' 当作常量来推
let a = 'b' as const
有的时候,我们声明一个变量又想它是一个常量
const array = [1, 'hi'] 请问这个数组的类型是什么?
根据逻辑推断应该是 readonly [number, string]
为何是第一个呢?
const array = [1, 'hi']
array.push(2) // 这个可以改, 它只是 const 了 array, array 里面的属性它无法 const
// 它不能保证 array 里面的东西保持不变,它只能保证 array 不会被重新赋值而已
// 1 (number | string)[]
// 2 readonly [number, string]
也就是说 TS 的 const 才是真的 const, 完全静态, 不能改任何东西,但是 JS 的 const 可以改。
那我想让它数组里面的东西也保持不变,于是 as const 的需求场景来了
const array = [1, 'hi'] as const
这样的话它的类型就非常的窄了
以上例子证明我们确实需要一个 as const 来告诉 TS,它的类型不会变,我不会对 array 进行 push 。
如果 push 就会报错
为什么 长度为 2 的数组无法赋值给两个参数?
number[]类型太大了,我可以 a.push(3) 那么不就破坏了这个类型的约束了吗。
这样就可以对类型进行了收窄
当使用展开的时候,虽然数量是对的,但是依然报错了,就很有可能是因为你没有加 as const,导致展开的数量被推测错误了。
所以 as const 经常加到对象里(数组对象、普通对象...)
函数的更多细节
参数对象析构
type Config = {
url: string
method: 'GET' | 'POST' | 'PATCH' | 'DELETE'
}
/*
function ajax(config: Config) { // 那么我们希望在第 5 行这里就给它析构
const {url, method} = config
console.log(url, method)
}
*/
function ajax({ url, method }: Config) {
console.log(url, method)
}
// 如果 Config 还有其他的参数,那么能不能用剩余参数把它包起来?
// 如果加默认值呢?
type Config = {
url: string
method: 'GET' | 'POST' | 'PATCH' | 'DELETE'
data?: unknown
head?: unknown
}
// 类型加在左边
function ajax({
url, method, ...rest
}: Config = { method: 'GET', url: '' }) {
console.log(url, method)
}
// 类型加在右边默认值
function ajax({
url, method, ...rest
} = { method: 'GET', url: '' } as Config) {
console.log(url, method)
}
// 也可以都加,但二选一即可
void 返回值
当我们接收返回值是 void 的时候
// 第一种什么都不做, 不报错
function f(): void {
return
}
// 第二种 return 一个 undefined 也不报错
function f2(): void {
return undefined
}
// 第三种 不 return 也不报错
function f3(): void {
}
// 第四种 如果我们 return 一个 null 呢?
function f4(): void {
return null // 报错
}
// 这里使用的 strictNullChecks 为严格
// 也就是说 void 上面 3 种 是接受的,但是你 return null 就不行