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)