6. 函数的用法
约 6888 字大约 23 分钟
2025-05-19
一、简介
1、基础函数声明
函数的类型声明,需要在声明函数时,给出参数的类型和返回值的类型。
function greet(name: string): string {
return `Hello, ${name}!`;
}
我们在声明函数时,一般要指定函数的类型,包括参数列表、参数类型,返回值类型。如果不指定对应的类型,TypeScript 会自动推断,但是这不一定会符合我们预期。
function hello(txt) {
console.log("hello " + txt);
}
可能会被推断为:
function hello(txt: any): void {
console.log("hello " + txt);
}
2、箭头函数
箭头函数是普通函数的一种简化写法,它的类型写法与普通函数类似。
// 写法一
(参数列表): 返回值类型 => {
多行函数体;
};
// 写法二
(参数列表): 返回值类型 => 单行函数体;
例如下边代码:
变量 repeat
被赋值为一个箭头函数,类型声明写在箭头函数的定义里面。其中,参数的类型写在参数名后面,返回值类型写在参数列表的圆括号后面。
const repeat = (str: string, times: number): string => str.repeat(times);
注意,类型写在箭头函数的定义里面,与使用箭头函数表示函数类型,写法有所不同。
function greet(fn: (a: string) => void): void {
fn("world");
}
上面示例中,函数greet()
的参数fn
是一个函数,类型就用箭头函数表示。这时,fn
的返回值类型要写在箭头右侧,而不是写在参数列表的圆括号后面。
下面再看一个将一个 string[]
转化为 Person[]
的例子,能够更好的辅助我们理解箭头函数表达式的格式。
type Person = { name: string };
const people = ["alice", "bob", "jan"].map((name): Person => ({ name }));
这里主要着重强调一下 map()
方法的参数部分 (name): Person => ({ name })
。
(name)
代表的是参数列表,其中参数类型省略了,由 TypeScript 自动推断,括号不能省略,这是箭头函数类型定义的一部分。({name})
,代表返回一个象,这里的圆括号是额外添加的,因为{ name }
会被识别为包含一行语句name
且缺少return
语句的的函数体,这个函数不会返回任何值。
我们将它的类型推断补充完整,来辅助理解:
type Person = { name: string };
const people: Person[] = ["alice", "bob", "jan"].map(
(name: string): Person => ({ name })
);
注意,下面两种写法在本例子都是报错的。
(name: Person) => ({ name });
name: (Person) => ({ name });
- 第一种写法中,箭头函数的参数
name
类型指定为Person
,但是实际上是string
- 第二种写法中,函数参数缺少圆括号,导致
name
被识别为变量,而不是箭头函数表达式的参数。
3、函数表达式
函数表达式有两种写法。
// 写法一 定义函数体的同时,指定函数类型
const hello = function (txt: string): void {
console.log("hello " + txt);
};
// 写法二 先指定函数类型,再定义函数体
const hello: (txt: string) => void = function (txt) {
console.log("hello " + txt);
};
其中写法二中的 (txt: string) => void
是一个完整的函数类型定义,我们可以利用类型别名 type
,将它定义为一个类型。
type myFunc = (txt: string) => void;
const hello: myFunc = function (txt) {
console.log("hello " + txt);
};
函数表达式也支持分段式写法,即先定义函数类型,再定义函数体。
let f: (x: number) => number;
f = function (y: number) {
return y;
};
同时,函数类型定义里面的参数名与实际参数名,可以不一致:上面示例中,函数类型里面的参数名为 x,实际的函数定义里面,参数名为 y,两者并不相同。
4、Function
类型
TypeScript 提供 Function
类型表示函数,任何函数都属于这个类型。
function doSomething(f: Function) {
return f(1, 2, 3);
}
上面示例中,参数f
的类型就是 Function
,代表这是一个函数。
Function
类型的值都可以直接执行。
Function
类型的函数可以接受任意数量的参数,每个参数的类型都是 any
,返回值的类型也是 any
,代表没有任何约束,所以不建议使用这个类型,给出函数详细的类型声明会更好。
二、函数类型
我们可以将函数的类型单独作为一个类型,然后使用这个类型声明函数。
1、两种写法
typescript 中函数类型的格式有两种:
(参数列表) => 返回值;
{
(参数列表): 返回值;
}
注意两种写法的区别,第一个是 =>
,第二个是 :
。同时还需要注意参数列表的圆括号不能省略,否则会被当成变量识别,引发意料之外的问题。
2、type
类型别名
我们可以使用类型别名来定义函数类型,两种写法都支持。
type fun_type = (x: number) => number;
type fun_type = {
(x: number): number;
};
使用函数类型的声明函数的方式也很简单,基本等同于函数表达式的写法:
let show: fun_type = function (x: number) {
return x;
};
// 函数表达式写法
let show2: { (x: number): number } = (x: number): number => x;
3、interface
接口
函数类型也可以使用接口来定义,只能定义对象格式的函数类型,写法也类似函数表达式。
interface fun_type {
(x: number): number;
}
// 使用类型别名
let show: fun_type = function (x: number) {
return x;
};
4、typeof
运算符
TypeScript 允许使用 typeof
运算符获取一个变量或对象的类型。因此,如果一个变量要套用另一个函数类型,可以使用 typeof
运算符。
function add(x: number, y: number) {
return x + y;
}
const myAdd: typeof add = function (x, y) {
return x + y;
};
上面示例中,将函数 add
的类型使用 typeof
运算符获取,然后赋值给变量 myAdd
。
感觉比较绕的话,将 add()
的类型提取出来,起一个类型别名,使用类型别名来声明函数。
function add(x: number, y: number) {
return x + y;
}
type add_type = typeof add;
const myAdd: add_type = function (x, y) {
return x + y;
};
这是一个很有用的技巧,任何需要类型的地方,都可以使用 typeof
运算符从一个值获取类型。
5、带属性的函数
使用对象写法的函数类型还可以给函数定义一些额外属性,虽然这种场景比较少见:
interface AAA {
(x: number): void;
version: string;
}
const aaa: AAA = (x: number) => {
console.log(x);
};
aaa.version = "1.0.0";
三、函数特性
1、允许省略参数
JavaScript 函数在声明时往往有多个参数,实际可以只传入一部分参数。比如,数组的 forEach()
方法的参数是一个函数,该函数默认有三个参数 (item, index, array) => void
,实际上往往只使用第一个参数 (item) => void
。因此,TypeScript 也允许函数传入的参数不足。
函数允许参数名和类型定义不一致,所以函数的参数类型不是按照参数名匹配,而是按照下标匹配。
函数的实际参数个数,可以少于指定个数,但是不能多出来,同时被省略的参数必须位于参数列表的结尾。
let myFunc: (a: string, b: number) => string;
myFunc = (a: string, b: number, c: number) => "1"; // 报错Type '(a: string, b: number, c: number) => string' is not assignable to type '(a: string, b: number) => string'.
Target signature provides too few arguments. Expected 3 or more, but got 2.
myFunc = (a: string) => "1"; // 正确
myFunc = (b: number) => "1"; // 报错Type '(b: number) => string' is not assignable to type '(a: string, b: number) => string'.
Types of parameters 'b' and 'a' are incompatible.
Type 'string' is not assignable to type 'number'.
上面示例中,变量 myFunc
只能接受两个参数,如果传入三个参数,就会报错。如果省略 a
参数,也会报错,看似省略了 a
,其实省略了 b
,等同于给 a
参数传入了 number
类型数据。
如果你想省略参数 a
,只能显式的传入 undefined
,不能直接省略。
myFunc(undefined, 123);
参数省略,也可以体现在类型检查上:
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // 正确
x = y; // 报错
上面示例中,函数x
只有一个参数,函数y
有两个参数,x
可以赋值给y
,反过来就不行。
2、可选参数
如果函数的某个参数可以省略、可以为空,我们可以使用可选参数来表示。
在参数名后面加问号 ?
,表示该参数为可选参数。
function f(x?: number) {
console.log(x);
}
f(); // OK
f(10); // OK
上面示例中,参数x
后面有问号,表示该参数可以省略。在 TypeScript 中,x
的类型实际上被解析为 number|undefined
。
function f(x?: number) {
return x;
}
f(undefined); // 正确
函数的可选参数只能在参数列表的尾部,跟在必选参数的后面。这不仅是语法规则,也是为了保证函数的可读性。
let myFunc1: (a?: number, b: number) => number; // 报错
let myFunc2: (a: number, b?: number) => number; // 正确
函数体内部用到可选参数时,需要判断该参数是否为undefined
,避免直接使用带来意想不到的报错。
let myFunc: (a: number, b?: number) => number;
myFunc = function (x, y) {
if (y === undefined) {
return x;
}
return x + y;
};
上面示例中,由于函数的第二个参数为可选参数,所以函数体内部需要判断一下,该参数是否为空。
3、参数默认值
TypeScript 函数的参数默认值写法,与 JavaScript 一致。
设置了默认值的参数,就是可选的。如果不传入该参数,它就会等于默认值。
function createPoint(x: number = 0, y: number = 0): [number, number] {
return [x, y];
}
createPoint(); // [0, 0]
上面示例中,参数 x
和 y
的默认值都是 0
,调用 createPoint()
时,这两个参数都是可以省略的。这里其实可以省略 x
和 y
的类型声明,因为可以从默认值推断出来。
function createPoint(x = 0, y = 0) {
return [x, y];
}
可选参数与默认值不能同时使用。因为有了默认值,就意味着可选,就没必要再设置为可选了。
// 报错
function f(x?: number = 0) {
// ...
}
上面示例中,x
是可选参数,还设置了默认值,结果就报错了。
设有默认值的参数,如果传入 undefined
,也会触发默认值。
function f(x = 456) {
return x;
}
f(undefined); // 456
如果具有默认值的参数没有位于参数列表的末尾,调用时不能被省略,如果要触发默认值,必须显式传入 undefined
。
function add(x: number = 0, y: number) {
return x + y;
}
add(1); // 报错 少了一个参数Expected 2 arguments, but got 1.add(undefined, 1); // 2
4、rest
剩余参数
rest 参数表示函数剩余的所有参数,它可以是数组(剩余参数类型相同),也可能是元组(剩余参数类型不同)。
// rest 参数为数组
function joinNumbers(...nums: number[]) {
// ...
}
// rest 参数为元组
function f(...args: [boolean, number]) {
// ...
}
注意,元组需要声明每一个剩余参数的类型。如果元组里面的参数是可选的,则要使用可选参数。
function f(...args: [boolean, string?]) {}
下面是一个 rest 参数的例子。
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
上面示例中,参数m
就是 rest
参数类型,它的类型是一个数组。
rest
参数甚至可以嵌套。
function f(...args: [boolean, ...string[]]) {
// ...
}
上边代码中,rest 参数的类型是一个元组,第一个参数是 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);
}
5、readonly
只读参数
如果函数内部不能修改某个参数,可以在函数定义时,在参数类型前面加上readonly
关键字,表示这是只读参数。
function arraySum(arr: readonly number[]) {
// ...
arr[0] = 0; // 报错
}
上面示例中,参数arr
的类型是readonly number[]
,表示为只读参数。如果函数体内部修改这个数组,就会报错。
注意,readonly
关键字目前只允许用在数组和元组类型的参数前面,如果用在其他类型的参数前面,就会报错。
6、参数解构
函数参数如果存在变量解构,类型写法如下。
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);
}
7、参数别名
JavaScript 中允许函数的入参使用别名,即给参数指定一个别名。某些场景这个与 TypeScript 中的类型定义有冲突。
比如,下面的代码是 JavaScript 允许的。但是在 TypeScript 中,代码会报错。
function sum(a: valA, b) {
console.log(valA + b);
}
sum(1, 2); // 3
- 如果恰好存在
valA
的定义,报TS 2693
错误,即把类型当做值使用了。 - 如果不存在
valA
的定义,报TS 7006
错误,即找不到类型valA
的定义。
type valA = number;增加 valA 的定义只是为了让 TypeScript 报 2693 错,辅助理解 valA 被识别为了类型function sum(a: valA, b) {
console.log(valA + b);'valA' only refers to a type, but is being used as a value here.}
在 TypeScript 中,a: number
被类型定义占用了,(a: number): valA
被函数定义占用了,这种情况下,类型 sum(a: valA, b: valB)
是无法被识别成函数别名的。
看到这,我建议和自己和解一下,别去抠怎么解决这个问题,因为 TypeScript 不允许这样使用参数别名。
首先,在 TypeScript 中,函数对参数的名称,并没有强制要求,允许和定义里的参数名称不一致。
再者,给函数的形参起一个别名,如 sum(a: valA, b: valB)
,这种代码没有实质意义。
如果参数是个对象,对它进行解构,并对子属性使用别名,这是允许的,能够有效的提高代码的可读性。
例如:
type Obj = {
a: number;
b: number;
};
function sum({ a: valA, b }: Obj) {
console.log(valA + b);
}
let obj: Obj = {
a: 1,
b: 2,
};
sum(obj);
或者使用元组
type Point3D = [x: number, y: number, z: number];
function drawPoint([xPos, yPos, zPos]: Point3D) {
console.log(`Position: (${xPos}, ${yPos}, ${zPos})`);
}
// 调用
drawPoint([10, 20, 30]);
7、void
类型
void
类型表示函数没有返回值。
function f(): void {
console.log("hello");
}
上面示例中,函数 f
没有返回值,类型就要写成 void
。
如果返回其他值,就会报错。
function f(): void {
return 123; // 报错
}
上面示例中,函数 f()
的返回值类型是 void
,但是实际返回了一个数值,编译时就报错了。
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; // 报错
}
需要特别注意的是,如果变量、对象方法、函数参数是一个返回值为 void
类型的函数,那么并不代表不能赋值为有返回值的函数。恰恰相反,该变量、对象方法和函数参数可以接受返回任意值的函数,这时并不会报错。
type voidFunc = () => void;
const f: voidFunc = () => {
return 123;
};
上面示例中,变量 f
的类型是 voidFunc
,是一个没有返回值的函数。但是实际上,f
的值可以是一个有返回值的函数(返回 123
),编译时不会报错。
这是因为,这时 TypeScript 认为,这里的 void
类型只是表示该函数的返回值没有利用价值,或者说不应该使用该函数的返回值。只要不用到这里的返回值,就不会报错。
这样设计是有现实意义的。举例来说,数组方法 Array.prototype.forEach(fn)
的参数 fn
是一个函数,而且这个函数应该没有返回值,即返回值类型是 void
。
但是,实际应用中,很多时候传入的函数是有返回值,但是它的返回值不重要,或者不产生作用。
const src = [1, 2, 3];
const ret = [];
src.forEach((el) => ret.push(el));
上面示例中,push()
有返回值,表示插入新元素后数组的长度。但是,对于forEach()
方法来说,这个返回值是没有作用的,根本用不到,所以 TypeScript 不会报错。
如果后面使用了这个函数的返回值,就违反了约定,则会报错。
type voidFunc = () => void;
const f: voidFunc = () => {
return 123;
};
f() * 2; // 报错
上面示例中,最后一行报错了,因为根据类型声明,f()
没有返回值,但是却用到了它的返回值,因此报错了。
注意,这种情况仅限于变量、对象方法和函数参数,函数字面量如果声明了返回值是 void 类型,还是不能有返回值。
function f(): void {
return true; // 报错
}
const f3 = function (): void {
return true; // 报错
};
上面示例中,函数字面量声明了返回void
类型,这时只要有返回值(除了undefined
和null
)就会报错。
函数的运行结果如果是抛出错误,也允许将返回值写成void
。
function throwErr(): void {
throw new Error("something wrong");
}
上面示例中,函数throwErr()
会抛出错误,返回值类型写成void
是允许的。
除了函数,其他变量声明为void
类型没有多大意义,因为这时只能赋值为undefined
或者null
(假定没有打开strictNullChecks
) 。
let foo: void = undefined;
// 没有打开 strictNullChecks 的情况下
let bar: void = null;
8、never
类型
never
类型表示肯定不会出现的值。它用在函数的返回值,就表示某个函数肯定不会返回值,即函数不会正常执行结束。
它主要有以下两种情况。
(1)抛出错误的函数。
function fail(msg: string): never {
throw new Error(msg);
}
上面示例中,函数 fail()
会抛出错误,不会正常退出,所以返回值类型是 never
。
注意,只有抛出错误,才是 never
类型。如果显式用 return
语句返回一个 Error
对象,返回值就不是 never
类型。
function fail(): Error {
return new Error("Something failed");
}
上面示例中,函数 fail()
返回一个 Error
对象,所以返回值类型是 Error
。
另外,由于抛出错误的情况属于 never
类型或 void
类型,所以无法从返回值类型中获知,抛出的是哪一种错误。
(2)无限执行的函数。
const sing = function (): never {
while (true) {
console.log("sing");
}
};
上面示例中,函数 sing()
会永远执行,不会返回,所以返回值类型是 never
。
注意,never
类型不同于 void
类型。前者表示函数没有执行结束,不可能有返回值;后者表示函数正常执行结束,但是不返回值,或者说返回 undefined
。
// 正确
function sing(): void {
console.log("sing");
}
// 报错
function sing(): never {
console.log("sing");
}
上面示例中,函数 sing()
虽然没有 return
语句,但实际上是省略了 return undefined
这行语句,真实的返回值是 undefined
。所以,它的返回值类型要写成 void
,而不是 never
,写成 never
会报错。
如果一个函数抛出了异常或者陷入了死循环,那么该函数无法正常返回一个值,因此该函数的返回值类型就是 never
。如果程序中调用了一个返回值类型为 never
的函数,那么就意味着程序会在该函数的调用位置终止,永远不会继续执行后续的代码。
function neverReturns(): never {
throw new Error();
}
function f(x: string | undefined) {
if (x === undefined) {
neverReturns();
}
x; // 推断为 string
}
上面示例中,函数 f()
的参数 x
的类型为 string|undefined
。但是,x
类型为 undefined
时,调用了 neverReturns()
。这个函数不会返回,因此 TypeScript 可以推断出,判断语句后面的那个 x
,类型一定是 string
。
一个函数如果某些条件下有正常返回值,另一些条件下抛出错误,这时它的返回值类型可以省略 never
。
function sometimesThrow(): number {
if (Math.random() > 0.5) {
return 100;
}
throw new Error("Something went wrong");
}
const result = sometimesThrow();
上面示例中,函数 sometimesThrow()
的返回值其实是 number|never
,但是一般都写成 number
,包括最后一行的变量 result
的类型,也是被推断为number
。
原因是前面章节提到过,never
是 TypeScript 的唯一一个底层类型,所有其他类型都包括了 never
。从集合论的角度看,number|never
等同于 number
。这也提示我们,函数的返回值无论是什么类型,都可能包含了抛出错误的情况。
9、局部作用域
函数内部允许声明类型和变量,且只在函数内部有效,我们可以称为局部作用域。
function hello(txt: string) {
type message = string;
let newTxt: message = "hello " + txt;
return newTxt;
}
const newTxt: message = hello("world"); // 报错Cannot find name 'message'.console.log(newTxt); // undefined
上面示例中,类型message
是在函数hello()
内部定义的,只能在函数内部使用。在函数外部使用,就会报错。
四、构造函数
JavaScript 语言使用构造函数,生成对象的实例。
构造函数的最大特点,就是必须使用 new
命令调用。
const d = new Date();
上面示例中,Date()
就是一个构造函数,使用 new
命令调用,返回 Date 对象的实例。
构造函数的类型写法,就是在参数列表前面加上 new
命令。
class Animal {
numLegs: number = 4;
}
type AnimalConstructor = new () => Animal;
function create(c: AnimalConstructor): Animal {
return new c();
}
const a = create(Animal);
上面示例中,类型 AnimalConstructor
就是一个构造函数,而函数 create()
需要传入一个构造函数。在 JavaScript 中,类(class
)本质上是构造函数,所以 Animal
这个类可以传入 create()
。
构造函数还有另一种类型写法,就是采用对象形式。
type F = {
new (s: string): object;
};
上面示例中,类型 F 就是一个构造函数。类型写成一个可执行对象的形式,并且在参数列表前面要加上 new
命令。
某些函数既是构造函数,又可以当作普通函数使用,比如 Date()
。这时,类型声明可以写成下面这样。
type F = {
new (s: string): object;
(n?: number): number;
};
上面示例中,F 既可以当作普通函数执行,也可以当作构造函数使用。
五、函数重载
有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为。这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)。
reverse("abc"); // 'cba'
reverse([1, 2, 3]); // [3, 2, 1]
上面示例中,函数 reverse()
可以将参数颠倒输出。参数可以是字符串,也可以是数组。
这意味着,该函数内部有处理字符串和数组的两套逻辑,根据参数类型的不同,分别执行对应的逻辑。这就叫“函数重载”。
TypeScript 对于“函数重载”的类型声明方法是,逐一定义每一种情况的类型。
function reverse(str: string): string;
function reverse(arr: any[]): any[];
上面示例中,分别对函数 reverse()
的两种参数情况,给予了类型声明。但是,到这里还没有结束,后面还必须对函数 reverse()
给予完整的类型声明。
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();
}
上面示例中,前两行列举了重载的各种情况。第三行的函数则是对重载的实现,它的类型声明必须与前边的所有情况兼容。
有一些编程语言如 Java, Java 允许有不同的函数参数,对应不同的函数实现,使用 overload
标记。但是,JavaScript 函数只能有一个实现,必须在这个实现当中,兼容所有情况,处理不同的参数。因此,函数体内部就需要使用 if / else
语句 判断参数的类型及个数,并根据判断结果执行不同的操作。
function add(x: number, y: number): number;
function add(x: any[], y: any[]): any[];
function add(x: number | any[], y: number | any[]): number | any[] {
if (typeof x === "number" && typeof y === "number") {
return x + y;
} else if (Array.isArray(x) && Array.isArray(y)) {
return [...x, ...y];
}
throw new Error("wrong parameters");
}
注意,重载的各个类型描述与函数的具体实现之间,不能有其他代码,否则报错。
另外,虽然函数的具体实现里面,有完整的类型声明。但是,函数实际调用的类型,以重载的类型声明为准。比如,上例的函数实现,参数类型和返回值类型都是 number | any[]
,但不意味着参数类型为 number
时返回值类型为 any[]
。
函数重载的每个类型声明之间,以及类型声明与函数实现的类型之间,不能有冲突。
function fn(x: boolean): void;This overload signature is not compatible with its implementation signature.function fn(x: string): void;
function fn(x: number | string) {
console.log(x);
}
上面示例中,函数重载的类型声明与函数实现是冲突的,没有包含 boolean
的情况,导致报错。
重载声明的排序很重要,因为 TypeScript 是按照顺序进行检查的,一旦发现符合某个类型声明,就不再往下检查了,所以类型最宽的声明应该放在最后面,防止覆盖其他类型声明。
function f(x: any): number;
function f(x: string): 0 | 1;
function f(x: any): any {
// ...
}
const a: 0 | 1 = f("hi"); // 报错Type 'number' is not assignable to type '0 | 1'.
上面声明中,第一行类型声明x:any
范围最宽,导致函数f()
的调用都会匹配这行声明,无法匹配第二行类型声明,所以最后一行调用就报错了,因为等号两侧类型不匹配,左侧类型是0|1
,右侧类型是number
。这个函数重载的正确顺序是,第二行类型声明放到第一行的位置。
对象的方法也可以使用重载。
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;
}
}
上面示例中,方法add()
也使用了函数重载。
函数重载也可以用来精确描述函数参数与返回值之间的对应关系。
function createElement(tag: "a"): HTMLAnchorElement;
function createElement(tag: "canvas"): HTMLCanvasElement;
function createElement(tag: "table"): HTMLTableElement;
function createElement(tag: string): HTMLElement {
// ...
}
上面示例中,函数重载精确描述了参数tag
的三个值,所对应的不同的函数返回值。
这个示例的函数重载,也可以用对象表示。
type CreateElement = {
(tag: "a"): HTMLAnchorElement;
(tag: "canvas"): HTMLCanvasElement;
(tag: "table"): HTMLTableElement;
(tag: string): HTMLElement;
};
由于重载是一种比较复杂的类型声明方法,为了降低复杂性,一般来说,如果可以的话,应该优先使用联合类型替代函数重载,除非多个参数之间、或者某个参数与返回值之间,存在对应关系。
实际工作中,很少用到重载,要么使用联合类型,要么以多个不同的函数名来代替。
// 写法一
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any): number {
return x.length;
}
// 写法二
function len(x: any[] | string): number {
return x.length;
}
上面示例中,写法二使用联合类型,要比写法一的函数重载简单很多。
六、高阶函数
一个函数的返回值还是一个函数,那么前一个函数就称为高阶函数(higher-order function)。
下面就是一个例子,箭头函数返回的还是一个箭头函数。
(someValue: number) => (multiplier: number) => someValue * multiplier;
更新日志
e7112
-1于