函数
1876字约6分钟
TypeScript
2023-01-30
✨函数类型
函数的类型声明,需要在声明函数时,给出参数的类型和返回值的类型。
function hello(txt: string): void {
console.log("hello " + txt);
}
如果变量被赋值为一个函数,变量的类型有两种写法。
// 写法一
const hello = function (txt: string) {
console.log("hello " + txt);
};
// 写法二
const hello: (txt: string) => void = function (txt) {
console.log("hello " + txt);
};
// 写法二用 type 运算符优化
type Hello = (txt: string) => void;
const hello: Hello = function (txt) {
console.log("hello " + txt);
}
注意
函数的实际参数个数,可以少于类型指定的参数个数,但是不能多于,即 TypeScript 允许省略参数。
let myFunc: (a: number, b: number) => number;
myFunc = (a: number) => a; // 正确
myFunc = (a: number, b: number, c: number) => a + b + c; // 报错
函数类型还可以采用对象的写法, 这种写法平时很少用,但是非常合适用在一个场合:函数本身存在属性。
{
(参数列表): 返回值
}
let add: {
(x: number, y: number): number; // 对象的写法
version: number;
};
add = function (x, y) {
return x + y;
};
add.version = 1;
✨Function 类型
TypeScript 提供 Function
类型表示函数,任何函数都属于这个类型。
function doSomething(f: Function) {
return f(1, 2, 3);
}
上面示例中,参数f的类型就是Function
,代表这是一个函数。
Function
类型的值都可以直接执行。
Function
类型的函数可以接受任意数量的参数,每个参数的类型都是any
,返回值的类型也是any
,代表没有任何约束,所以不建议使用这个类型,给出函数详细的类型声明会更好。
✨箭头函数
箭头函数是普通函数的一种简化写法,它的类型写法与普通函数类似。
注意,类型写在箭头函数的定义里面,与使用箭头函数表示函数类型,写法有所不同。
// 类型写在箭头函数的定义里面
函数名(参数列表): 返回值类型 => 函数体
const repeat = (str: string, times: number): string => str.repeat(times);
// 使用箭头函数表示函数类型
函数名: (参数列表) => 返回值类型
function greet(fn: (a: string) => void): void {
fn("world");
}
✨可选参数
如果函数的某个参数可以省略,则在参数名后面加问号表示。
function f(x?: number) {
return x;
}
f(undefined); // 正确
// 类型显式设为 undefined 的参数
function f(x: number | undefined) {
return x;
}
f(); // 报错
参数名带有问号,表示该参数的类型实际上是原始类型|undefined
,它有可能为undefined
。比如,上例的x虽然类型声明为number
,但是实际上是number|undefined
。
但是,反过来就不成立,类型显式设为undefined
的参数,就不能省略。
函数体内部用到可选参数时,需要判断该参数是否为undefined
。
let myFunc: (a: number, b?: number) => number;
myFunc = function (x, y) {
if (y === undefined) {
return x;
}
return x + y;
};
✨参数默认值
TypeScript 函数的参数默认值写法,与 JavaScript 一致。
function createPoint(x: number = 0, y: number = 0): [number, number] {
return [x, y];
}
createPoint(); // [0, 0]
注意
可选参数与默认值不能同时使用。
✨参数解构
函数参数如果存在变量解构,类型写法如下。
function f([x, y]: [number, number]) {
// ...
}
function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c);
}
参数结构可以结合类型别名(type 命令
)一起使用,代码会看起来简洁一些。
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}
✨rest 参数
rest
参数表示函数剩余的所有参数,它可以是数组(剩余参数类型相同),也可能是元组(剩余参数类型不同)。
// rest 参数为数组
function joinNumbers(...nums: number[]) {
// ...
}
// rest 参数为元组
function f(...args: [boolean, number]) {
// ...
}
注意,元组需要声明每一个剩余参数的类型。如果元组里面的参数是可选的,则要使用可选参数。
function f(...args: [boolean, string?]) {}
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
rest 参数甚至可以嵌套。
function f(...args: [boolean, ...string[]]) {
// ...
}
rest 参数可以与变量解构结合使用。
function repeat(...[str, times]: [string, number]): string {
return str.repeat(times);
}
// 等同于
function repeat(str: string, times: number): string {
return str.repeat(times);
}
✨readonly 只读参数
function arraySum(arr: readonly number[]) {
// ...
arr[0] = 0; // 报错
}
✨void 类型
void 类型表示函数没有返回值。
function f(): void {
console.log("hello");
}
void
类型允许返回undefined
或null
。
function f(): void {
return undefined; // 正确
}
function f(): void {
return null; // 正确
}
注意
如果打开了strictNullChecks
编译选项,那么 void
类型只允许返回undefined
。如果返回null
,就会报错。这是因为 JavaScript 规定,如果函数没有返回值,就等同于返回undefined
。
// 打开编译选项 strictNullChecks
function f(): void {
return undefined; // 正确
}
function f(): void {
return null; // 报错
}
✨never 类型
never
类型表示肯定不会出现的值。它用在函数的返回值,就表示某个函数肯定不会返回值,即函数不会正常执行结束。它主要有以下两种情况:
(1)抛出错误的函数。
function fail(msg: string): never {
throw new Error(msg);
}
注意,只有抛出错误,才是 never
类型。如果显式用return
语句返回一个 Error
对象,返回值就不是 never
类型。
(2)无限执行的函数。
const sing = function (): never {
while (true) {
console.log("sing");
}
};
✨高阶函数
一个函数的返回值还是一个函数,那么前一个函数就称为高阶函数(higher-order function)
。
(someValue: number) => (multiplier: number) => someValue * multiplier;
✨函数重载
有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为。这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)
。
函数重载的声明限制:
函数重载的每个类型声明之间,以及类型声明与函数实现的类型之间,不能有冲突。
类型最宽的声明应该放在最后面,防止覆盖其他类型声明。
function reverse(str: string): string;
function reverse(arr: any[]): any[];
function reverse(stringOrArray: string | any[]): string | any[] {
if (typeof stringOrArray === "string")
return stringOrArray.split("").reverse().join("");
else return stringOrArray.slice().reverse();
}
对象的方法也可以使用重载:
class StringBuilder {
#data = "";
add(num: number): this;
add(bool: boolean): this;
add(str: string): this;
add(value: any): this {
this.#data += String(value);
return this;
}
toString() {
return this.#data;
}
}
函数重载也可以用来精确描述函数参数与返回值之间的对应关系:
function createElement(tag: "a"): HTMLAnchorElement;
function createElement(tag: "canvas"): HTMLCanvasElement;
function createElement(tag: "table"): HTMLTableElement;
function createElement(tag: string): HTMLElement {
// ...
}
// 这个示例的函数重载,也可以用对象表示
type CreateElement = {
(tag: "a"): HTMLAnchorElement;
(tag: "canvas"): HTMLCanvasElement;
(tag: "table"): HTMLTableElement;
(tag: string): HTMLElement;
};
提示
由于重载是一种比较复杂的类型声明方法,为了降低复杂性,一般来说,如果可以的话,应该优先使用联合类型替代函数重载。
✨构造函数
构造函数的类型写法,就是在参数列表前面加上new
命令。
class Animal {
numLegs: number = 4;
}
type AnimalConstructor = new () => Animal;
function create(c: AnimalConstructor): Animal {
return new c();
}
const a = create(Animal);
构造函数还有另一种类型写法,就是采用对象形式:
type F = {
new (s: string): object;
};
某些函数既是构造函数,又可以当作普通函数使用,比如Date()。这时,类型声明可以写成下面这样:
type F = {
new (s: string): object;
(n?: number): number;
};
注意
TS不能用es5
的形式来创建构造函数,因为this
会报错。