TS函数

函数是 JavaScript 应用程序的基础,可以实现抽象层,模拟类,信息隐藏和模块。在 TypeScript 里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。TypeScript 为 JavaScript 函数添加了额外的功能,可以更容易地使用

基本示例

和 JavaScript 一样,TypeScript 也可以创建函数。

1
2
3
4
5
6
7
8
9
// 函数声明
function add(x, y) {
return x + y;
}

// 函数表达式
const myAdd = function (x, y) {
return x + y;
};

函数类型

定义函数类型

让我们为上面那个函数添加类型:

1
2
3
4
5
6
7
function add(x: number, y: number): number {
return x + y;
}

const myAdd = function (x: number, y: number): number {
return x + y;
};

我们可以给每个参数添加类型之后再为函数本身添加返回值类型。TypeScript 能够根据返回语句自动推断出返回值类型。

完整函数类型

现在我们已经为函数指定了类型,下面让我们写出函数的完整类型。

1
2
3
4
5
6
7
8
9
10
// 方式一
const myAdd2: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};

// 方式二
type addFnType = (x: number, y: number) => number;
const myAdd2: addFnTyep = function (x: number, y: number): number {
return x + y;
};

参数相关

可选参数

JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined。 在 TypeScript 里我们可以在参数名旁使用 ? 实现可选参数的功能

这个时候y的类型其实是 undefinednumber 类型的联合。

1
2
3
4
5
6
7
function add(X: number, y?: number): number {
if (y) {
return x + y;
} else {
return x;
}
}

默认参数

在 TypeScript 里,也可以为参数提供一个默认值,当用户没有传递这个参数或传递的值是 undefined 时。 它们叫做有默认初始化值的参数

1
2
3
function add(x: number, b: number = 6): number {
return x + y;
}

剩余参数

必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,想同时操作多个参数,或者并不知道会有多少参数传递进来。 在 JavaScript 里,你可以使用 arguments 来访问所有传入的参数。

在 TypeScript 里,你可以把所有参数收集到一个变量里:
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...)后面给定的名字,你可以在函数体内使用这个数组。

1
2
3
4
function info(x: string, ...args: string[]) {
console.log(x, args);
}
info("abc", "c", "b", "a");

函数重载

函数重载: 函数名相同, 而形参不同的多个函数
在 JS 中, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的 但在 TS 中, 与其它面向对象的语言(如 Java)就存在此语法

  • 在 TypeScript 中,可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用
  • 一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 需求: 只能将两个数字/两个字符串进行相加
// 案例分析: any实现
function add(arg1, arg2) {
return arg1 + arg2;
}

add(10, 20);
add("abc", "cba");
add({ aaa: "aaa" }, 123);

// 1.实现两个函数
function add1(num1: number, num2: number) {
return num1 + num2;
}

function add2(str1: string, str2: string) {
return str1 + str2;
}

add1(10, 20);
add2("abc", "cba");

// 2.错误的做法: 联合类型是不可以
function add(arg1: number | string, arg2: number | string) {
return arg1 + arg2;
}

// 3.TypeScript中函数的重载写法
// 3.1.先编写重载签名
function add(arg1: number, arg2: number): number;
function add(arg1: string, arg2: string): string;

// 3.2.编写通用的函数实现
function add(arg1: any, arg2: any): any {
return arg1 + arg2;
}

add(10, 20);
add("aaa", "bbb");
// 通用函数不能被调用
// add({name: "why"}, "aaa")
// add("aaa", 111)

需求:定义一个函数,可以传入字符串或者数组,获取它们的长度

两种实现方案

  • 方案一:使用联合类型来实现
  • 方案二:实现函数重载来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1.普通的实现
function getLength(arg) {
return arg.length;
}

// 2.函数的重载
function getLength(arg: string): number;
function getLength(arg: any[]): number;
function getLength(arg) {
return arg.length;
}

// 3.联合类型实现(可以使用联合类型实现的情况, 尽量使用联合类型)
function getLength(arg: string | any[]) {
return arg.length;
}

// 4.额外补充:对象类型实现
function getLength(arg: { length: number }) {
return arg.length;
}

getLength("aaaaa");
getLength(["abc", "cba", "nba"]);
getLength({ length: 100 });

调用签名

在 JavaScript 中,函数除了可以被调用,自己也是可以有属性值的

然而前面讲到的函数类型表达式并不能支持声明属性

如果想描述一个带有属性的函数,可以在一个对象类型中写一个调用签名(call signature

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1.函数类型表达式
type BarType = (num: number) => number;

// 2.函数的调用签名(从对象的角度来看待这个函数, 也可以有其他属性)
interface IBar {
name: string;
age: number;
(num: number): number; // 函数可以调用: 函数调用签名
}

const bar: IBar = (num: number): number => {
return num;
};

bar.name = "aaa";
bar.age = 18;
bar(123);

// 开发中如何选择:
// 1.如果只是描述函数类型本身(函数可以被调用), 使用函数类型表达式(Function Type Expressions)
// 2.如果在描述函数作为对象可以被调用, 同时也有其他属性时, 使用函数调用签名(Call Signatures)

构造签名

JavaScript 函数也可以使用 new 操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructor),因为 他们会产生一个新对象

  • 可以写一个构造签名( Construct Signatures ),方法是在调用签名前面加一个 new 关键词
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
constructor() {}
}

interface ICTORPerson {
new (): Person;
}

function factory(fn: ICTORPerson) {
const f = new fn();
return f;
}

factory(Person);

this 相关

函数中 this 默认类型

tsconfig.json设置了noImplicitThistrue时, TypeScript 会根据上下文推导this,但是在不能正确推导时,就会报错,需要明确的指定this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在没有对TS进行特殊配置的情况下, this是any类型

// 1.对象中的函数中的this
const obj = {
name: "why",
studying: function () {
// 默认情况下, this是any类型
console.log(this.name.length, "studying");
}
};

obj.studying();
// obj.studying.call({})

// 2.普通的函数
function foo() {
console.log(this);
}

函数中 this 明确类型

在开启noImplicitThis的情况下,必须指定this的类型

函数的第一个参数类型

  • 函数的第一个参数可以根据该函数之后被调用的情况,用于声明this的类型(名词必须叫this
  • 在后续调用函数传入参数时,从第二个参数开始传递的,this参数会在编译后被抹除
1
2
3
4
5
6
// 2.普通的函数
function foo(this: { name: string }, info: { name: string }) {
console.log(this, info);
}

foo.call({ name: "Tom" }, { name: "kobe" });

this 相关内置工具

Typescript 提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用

ThisParameterType

ThisParameterType

  • 用于提取一个函数类型Typethis (opens new window)参数类型
  • 如果这个函数类型没有this参数返回unknown
1
2
3
4
5
6
7
8
function foo(this: { name: string }, info: { name: string }) {
console.log(this, info);
}

type FooType = typeof foo;

// ThisParameterType: 获取FooType类型中参数this的类型
type FooThisType = ThisParameterType<FooType>; // type FooThisType = { name: string }

OmitThisParameter

OmitThisParameter

  • 用于移除一个函数类型Typethis参数类型, 并且返回当前的函数类型
1
2
3
4
5
6
7
8
function foo(this: { name: string }, info: { name: string }) {
console.log(this, info);
}

type FooType = typeof foo;

// OmitOmitThisParameter: 删除this参数类型, 剩余的函数类型
type PureFooType = OmitThisParameter<FooType>; // type PureFooType = (info: { name: string }) => void

ThisType

这个类型不返回一个转换过的类型,它被用作标记一个上下文的this类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ThisType: 用于绑定一个上下文的this
interface IState {
name: string;
age: number;
}

interface IStore {
state: IState;
eating: () => void;
running: () => void;
}

const store: IStore & ThisType<IState> = {
state: {
name: "aaa",
age: 18
},
eating() {
console.log(this.name);
},
running() {
console.log(this.name);
}
};

store.eating.call(store.state); // aaa