1.函数声明
TypeScript的函数声明形式比JavaScript要复杂一些
1.1 JavaScript函数声明
-
函数声明语句声明函数:
function test() {}
-
赋值语句声明函数:
const test = function(){}
-
声明箭头函数:
const test = () => {}
1.2 TypeScript函数声明
-
函数声明语句声明函数
-
赋值语句声明函数
-
声明箭头函数
-
接口或类型别名声明函数:
interface Test { (arg: string): void } // type Test = (arg: string): void // 使用类型别名描述 const test: Test = (arg) => {}
-
函数签名声明函数:
函数签名可以指定更多内容,除了指定函数体外,还可以指定函数属性
// 声明函数签名 type Func = { description: string; (arg: string): void; } // 实现指定函数 function func(name: string): void { console.log(name) } func.description = 'this is a function' // 实现函数签名 const fc: Func = func fc("Danny")
2.函数签名
函数签名用来描述函数详细信息,普通形式声明函数不能定义函数属性,使用函数签名可以实现。
2.1 调用签名
调用签名是普通的函数签名,借助类型别名或接口来实现。
2.1.1 包含属性的函数签名
// 声明函数签名
type Func = {
description: string;
(arg: string): void;
}
// 实现指定函数
function func(name: string): void {
console.log(name)
}
func.description = 'this is a function'
// 实现函数签名
const fc: Func = func
// 也可以使用接口
interfacre Func {
description: string,
(arg: string): void
}
2.1.2 简单的函数签名
// 类型别名
type Func = (name: string, age: number) => void
const func: Func = (n: string, a: number) => {}
// 类型别名简写
const func: (name: string, age: number) => void = (n: string, a: number) => {}
// 接口
interface Func {
(name: string, age: number): void
}
const func: Func = (n: string, a: number) => {}
// 接口简写
const func: { (name: string, age: number): void } = (n: string, a: number) => {}
2.2 构造签名
构造签名用于描述类的构造函数, 但构造函数不能通过构造签名声明,也不能实现构造签名。
构造签名的应用场景主要就是“工厂函数”,函数接收一个类,返回该类实例化的对象。
// 通过接口来描述构造签名
interface Constructor {
new (name: string, age: number): { name: string, age: number }
}
// 构造签名描述的构造函数所在的类
class Person {
constructor(public name: string, public age: number) {}
}
// 工厂函数
function factory(constructor: Constructor, name: string, age: number): Person {
return new constructor(name, age)
}
// 构造函数通过类名调用,所以将Person类传递过去
console.log(factory(Person, 'Danny', 21))
↓↓或者也可以指定类实现这个构造签名,但是没有什么实际意义
interface Constructor {
new (name: string, age: number): { name: string, age: number };
}
// 通过类型注释来说明类实现了构造签名
const Person: Constructor = class {
constructor(public name: string, public age: number) {}
}
2.3 重载签名
重载签名用于函数重载。不像是调用签名和构造签名都是由箭头函数或其语法糖来实现,重载签名只能由普通函数实现
function func(name: string, age: number): { name: string, age: number } // 没有函数体的普通函数就是重载签名
类中的构造函数不允许指定返回类型,所以和普通函数稍有区别
class Stu {
public name: string
public age: number
constructor(name: string, age: number) // 不指定返回类型
constructor() // 不指定返回类型
constructor(name?: string, age?: number) {
if(name) {
this.name = name
this.age = age !
} else {
this.name = "none"
this.age = 0
}
return this
}
}
3.函数可选参数
3.1 回调函数的可选参数
-
回调函数:
在JavaScript和TypeScript中,回调函数的特征可以简要描述为:函数作为另一个函数的参数。
-
不完全的静态类型检查:
TypeScript并不是完全做静态类型检查,在对于回调函数类型的检查中,静态类型检查这一标准变得宽泛。回调函数的实参个数可以小于形参可数。
-
在C++中对于形参实参个数的检查是严格的:
#include <iostream> using namespace std; void sayHello() { cout << "Hello, world!" << std::endl; } void callFunction(void (*functionPtr)(int a, int b)) { functionPtr(1, 2); } int main() { callFunction(&sayHello); // 报错,sayHello函数形参个数为0,callFunction中的形参函数要求2个参数 return 0; }
-
在TypeScript中,回调函数的实参个数可以小于形参个数:
因为在调用回调函数时,相当于传了多余的参数,这些参数会被忽略,一定不会产生错误。如果实参个数多于形参个数,例如下面第三个例子,那么在调用回调函数时,一定会少传参数,一定会报错。
const func = (arg: (name: string, age: number) => void): void => {} func(() => {}) // 合法的 func((name: number) => {}) // 非法的 func((name: string, age: number, gender: string) => {}) // 非法的
-
-
回调函数的形参不需要写可选参数:
上文提到了回调函数的实参可以小于等于其实参,这样的话没有必要在回调函数中写可选参数。
// 简易实现数组的过滤函数的部分功能 const filter = <T>(ar: Array<T>, func: (el: T, idx: number, ar: Array<T>) => boolean): Array<T> => ar.filter(func) const ar = [1, 2, 3, 4] console.log(filter(ar, el => !!(el % 2)))
下面的func中的el,idx,ar不需要再类型注释前添加问号。
const filter = <T>(ar: Array<T>, func: (el?: T, idx?: number, ar?: Array<T>) => boolean): Array<T> => ar.filter(func) // 没有必要的操作
4.函数剩余参数
4.1 剩余形参
剩余形参比较简单,注意类型注释一定是数组,因为在函数中使用剩余变量的时候是数组类型。
// 求和函数
function add(arg1: number, arg2: number, ...args: number[]): number { // 剩余形参类型注释为数组
return arg1 + arg2 + args.reduce((arg1, arg2) => arg1 + arg2, 0)
}
console.log(add(1, 2, 3, 4, 5, 6))
当不对剩余形参注释时,默认是any类型的数组。
// es5对于call的声明
call(this: Function, thisArg: any, ...argArray: any[]): any // 剩余形参为any类型数组
4.2 剩余实参
TypeScript类型推断会默认将数组推测为长度未知的数组,这样展开后传给函数的实参的个数是未知的,会导致报错。
function add(a: number, b: number) {
return a + b
}
const ar = [1, 2]
console.log(add(...ar)) // 报错,实参个数和形参个数不匹配
剩余实参要注意转化为不可变类型。
function add(a: number, b: number) {
return a + b
}
const ar = [1, 2] as const
console.log(add(...ar))
-
类比:
另一个类似的情景是在函数中使用泛型约束值,也需要强制类型转化。
// 修改对象length为尽可能小的值 function setMinLength<T extends { length: number }>(obj: T, minLength: number): T { if(obj.length < minLength) return obj return { length: minLength } as T // 需要强制类型转化为T类型。因为单纯包含length的对象不是T类型,T类型可能含有更多属性 }
-
回顾:
在枚举中使用常量枚举不会产生中间结果(反向映射)以提高效率。
const enum Fruit { Apple, Orange, Melon } console.log(Fruit) // 非法的,常量枚举不会产生中间结果Fruit console.log(Fruit.Apple) // 合法,只能访问枚举值或其关联值
5.函数参数解构
函数参数解构非常简单,只要在解构大括号之后进行类型注释即可。
不管解构多花里胡哨,有几层嵌套,是否有重命名,我们只需要关注解构在何处结束,然后在后面添加类型注释即可。
// 累加各科成绩
function add({math: a, biology: b, science: c}: { math: number, biology: number, science: number }): number {
return a + b + c
}
console.log(add({
math: 135,
biology: 74,
science: 67
}))
// 推荐将类型注释提取成类型别名或接口
interface StuGrade {
math: number,
biology: number,
science: number
}
-
类比:
使用解构提取值时我们不需要进行类型注释,此时类型是可以推断的
const stuGrade = { math: 135, biology: 74, science: 67 } const { math: a, biology: b, science: c } = stuGrade
6.函数参数this类型注释
TypeScript中函数除了可以对参数类型,返回值类型进行注释,还可以对this指向进行注释。
-
对this进行类型注释:
函数对this进行类型注释时,this作为第一个参数。
interface Stu { name: string, age: number } interface Person { gender: string, location: string, setStuInfo(this: Stu, name: string, age: number): void // 指定了setStuInfo函数中this必须是Stu类型 } const person: Person = { gender: 'male', location: 'China', setStuInfo(name: string, age: number) { console.log(this) } } person.setStuInfo('Danny', 21) // 报错,函数作为对象方法调用,this指向person对象,不是Stu
-
call和apply和bind无视this类型注释:
person.setStuInfo.call({}, 'Danny', 21) // 使用call调用上述函数,this强制切换为{},不会报错
7.函数返回值与void
-
类型注释声明的返回值为void类型的函数将不限制返回值:
const func: { (): void } = () => true // 合法,不会报错 console.log(func()) // 输出true
-
普通声明的返回值为void类型的函数限制返回值:
const func: () => void = () => true // 报错,返回值不应该为布尔类型
8.函数重载
函数重载就是同一个名称的函数可以实现不同的功能,它的参数和返回值都可以不同。
8.1 C++函数重载
直接声明多个同名函数,只是函数返回值和函数参数可以不同。
#include <bits/stdc++.h>
using namespace std;
int test(int a, int b) {
return a + b;
}
int test(int a, int b, int c) {
return a + b + c;
}
int main() {
cout << test(1, 2) << " " << test(1, 2, 3);
return 0;
}
8.2 TypeScript函数重载
-
普通函数重载:
直接声明多个同名函数是非法的。需要先声明多个函数的重载签名,再声明函数来实现所有的函数。
// 声明重载函数签名 function func(name: string, age: number): { name: string, age: number } // 声明重载函数签名 function func(a: number, b: number): number // 声明函数,实现所有重载函数签名 function func(arg1: string | number, arg2: number) { if(typeof arg1 === 'string') return { name: arg1, age: arg2 } return arg1 + arg2 }
-
通过函数签名重载:
interface Func { (a: number, b: number): number, (a: number, b: number, c: number): number } const func: Func = (a: number, b: number, c?: number) => c ? a + b + c : a + b
-
构造函数重载
TypeScript中构造函数不允许指定返回类型,所以重载构造函数时函数签名不包含返回类型。
// 构造函数签名 interface StuConstructor { new(id: string, name: string, age: number, password: string): { id: string, name: string, age: number, password: string } new(): { id: string, name: string, age: number, password: string } } const Stu: StuConstructor = class { public id: string public name: string public age: number public password: string // 构造函数重载 constructor(id: string, name: string, age: number, password: string) constructor() // 实现构造函数 constructor(id?: string, name?: string, age?: number, password?: string) { if(id) { this.id = id this.name = name! this.age = age! this.password = password! return this } this.id = '000' this.name = 'none' this.age = 21 this.password = '12345' } // Stu赋值为了匿名类,通过Symbol设置该匿名类的名称为Stu get [Symbol.toStringTag]() { return 'Stu' } } const stu = new Stu const stuD = new Stu('201900800164', 'Danny', 21, '79707536') console.log(stu, stuD)