简介

TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统

参考资料

快速入门

简介

  • 为什么要使用 TypeScript? 因为现在主流的前端框架都采用 TS 了,比如我们常见的 Vue React Angular,已然成为了一种趋势
  • TS是JS的超集,所以JS基础的类型都包含在内
  • 网上流传着一句话 始于 JavaScript,终于 JavaScript 因为浏览器是不认识 TypeScript 所以我们会把 TS 编译成 JS 最终运行的还是 js 文件

起步安装

  • 以下安装均采用 yarn 进行管理,如果是 npm 替换对应的命令即可
  • 安装 @types/node ts-node 是为了在开发环境直接使用命令运行 ts 文件,因为 node 不认识 ts ,如果不安装的话需要先用 tsc xxx.ts 进行编译 然后 node xxx.js 比较麻烦
1
2
3
4
5
6
7
8
9
# 全局安装 typescript 
yarn add typescript -g
# 查看 typescript 版本 当前使用的是 Version 4.6.4
tsc -v
# @types/node ts-node 支持
yarn add @types/node --save-dev
yarn add ts-node -g
# ts-node 版本查看 当前版本 v10.7.0
ts-node -v

类型约束

string

  • 约定为字符串类型
  • string 为小写的
1
2
3
4
5
6
7
//基本数据类型 string
let str: string = "定义一个字符串类型";
console.log(str);

//也可以使用 ES6 的字符串模板
let str2: string = `引用字符串模板-->${str}`
console.log(str2);

string类型

number

  • number 类型
  • 支持十六进制、十进制、八进制和二进制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 支持十六进制、十进制、八进制和二进制

//Nan
let notANumber: number = NaN;
console.log(notANumber);

//普通数字 如右所示 number 类型用字符串赋值就会报错 let num2: number = "123";
let num: number = 123;
console.log(num);
//无穷大
let infinityNumber: number = Infinity;
console.log(infinityNumber);

//十六进制
let hex: number = 0xf00d;
console.log(hex);
//二进制

let binary: number = 0b1010;
console.log(binary);
//八进制s

let octal: number = 0o744;
console.log(octal);

number类型

boolean

  • 注意,使用构造函数 Boolean 创造的对象不是布尔值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//new Boolean() 返回的是一个 Boolean 对象

let objBoolean: Boolean = new Boolean(1)
let falseBoolean: Boolean = new Boolean(false)
console.log(objBoolean);
console.log(falseBoolean);

//可以直接使用布尔值
let boolean: boolean = true
//也可以通过构造函数初始化返回布尔值
let boolean1: boolean = Boolean(1)

console.log(boolean);
console.log(boolean1);

boolean类型

void

  • JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数
  • void 类型的用法,主要是用在我们不希望调用者关心函数返回值的情况下,比如通常的异步回调函数
  • void也可以定义undefined 和 null类型
  • 运行前需要先编译成 js 文件用 node 运行(直接用 ts-node 运行 ts 文件会报错)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// void定义undefined 和 null类型
let u: void = undefined;
let n: void = null;
console.log(u, n);

// 返回值为 void 或者不返回
function voidFn(): void {
console.log('test void')
}

function voidFn2(): void {
console.log('test void2')
return
}

voidFn()
voidFn2()

void类型

null_undefined

  • 与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 null undefined 类型的变量,可以赋值给 string 类型的变量
  • void 类型不能赋值给其它类型,编译不会通过
  • 运行前需要先编译成 js 文件用 node 运行(直接用 ts-node 运行 ts 文件会报错)

null_undefined类型

any

  • any类型没有强制限定哪种类型,随时切换类型都可以 我们可以对 any 进行任何操作,不需要检查类型
  • 声明变量的时候没有指定任意类型默认为any
  • 弊端如果使用any 就失去了TS类型检测的作用
  • any 可以是对象,并且调用对应的属性或者方法
1
2
3
4
5
6
7
8
9
10
11
12
// any类型没有强制限定哪种类型,随时切换类型都可以 我们可以对 any 进行任何操作,不需要检查类型
let anyStr: any = "string";
let anyNum: any = 123;
let anyArray:any=[1,4,7]
let anyBoolean:any=true
let anySym:any=Symbol('123')

console.log(anyStr)
console.log(anyNum)
console.log(anyArray)
console.log(anyBoolean)
console.log(anySym)

any类型示例

unknown

  • TypeScript 3.0中引入的 unknown 类型也被认为是 top type ,但它更安全。与 any 一样,所有类型都可以分配给unknown
  • unknow unknow类型比any更加严格当你要使用any 的时候可以尝试使用unknow
  • unknow unknown定义的对象不能使用属性或者方法
  • unknown 类型的只能赋值给 unknown 或者 any 类型的数据
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
let unkStr: unknown = "str"
let unkNum: unknown = 123
let unkArray: unknown = [1, 4, 7]
let unkBoolean: unknown = true
let unkSym: unknown = Symbol('123')
console.log(unkStr)
console.log(unkNum)
console.log(unkArray)
console.log(unkBoolean)
console.log(unkSym)

let unkObj: unknown = {name: "ada", age: "25"}
let unkFun: unknown = {
fun: (): number => {
return 666
}
}
let anyObj: any = {name: "ada", age: "25"}
let anyFun: any = {
fun: (): number => {
return 888
}
}
// unknown 类定义的对象不能使用对应的属性和方法 any 类型的可以使用对应的属性
// console.log(unkObj.age)
// console.log(unkFun.fun());
console.log(anyObj.age)
console.log(anyFun.fun())

unknown类型

any与 unknown区别

  • unknown 类定义的对象不能使用对应的属性和方法 any 类型的可以使用对应的属性
  • unknown可赋值对象只有unknown 和 any
  • any 类型可赋值给其它任意类型

interface

基础示例

  • 在 typescript 中,我们定义对象的方式要用关键字interface(接口),我的理解是使用interface来定义一种约束,让数据的结构满足约束的格式
  • 如果有两个 interface 重名,那么会合并对象的属性
  • 使用接口约束的时候不能多一个属性也不能少一个属性,必须与接口保持一致
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
interface Person {
age: number
name: string
amount: number
}

const ada: Person =
{
age: 25,
name: "ada",
amount: 3000
}

interface device {
id: string
}

interface device {
name: string
}

const computer: device = {
id: "10010",
name: "Mac"
}
console.log(computer)
console.log(ada.age)

基础示例

可选式操作符

  • 上面的基本示例中,使用接口约束时必须要求属性和接口一致,如果有些属性希望可有可没有的需要使用可选式操作符 ? 来声明接口中的属性
  • 一般用做在一些新数据和老数据之间,老数据没有该字段
  • 可选属性的含义是该属性可以不存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 对于接口 Person ,并不是所有的对象都有 amount 属性,那么就可以用 ? 号标识为可选操作
interface Person {
age: number,
name: string,
amount?: number
}

// amount 是一个可选操作,所以不声明不会报错
const ada: Person =
{
age: 25,
name: "阿达"
}

const p: Person = {
age: 18,
name: "people",
amount: 36000
}

console.log(ada)
console.log(p)

可选操作符

任意属性proName

  • 当不确定后台返回的属性时,可用 proName 描述任意属性
  • 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Person {
age: number,
name: string,
amount?: number,

// any 任意类型 联合类型 string|number 范围为其中一种
[proName: string]: any
}

const ada: Person =
{
age: 25,
name: "阿达",
friends: "萨瓦迪卡"
}

const p: Person = {
age: 18,
name: "people",
amount: 36000
}
console.log(ada)
console.log(p)

proName任意属性

只读属性 readonly

  • readonly 只读属性,初始化后是不允许被赋值,只能读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Person {
age: number,
readonly name: string,
amount?: number,

// any 任意类型 联合类型 string|number 范围为其中一种
[proName: string]: any
}

const ada: Person =
{
age: 25,
name: "阿达",
friends: "萨瓦迪卡"
}

ada.age = 66
// ada.name = "ada" 会报错,因为上面定义了为 readonly 初始化对象后就不能再赋值了,只能读取不能修改
// ada.name = "ada"
console.log(ada)

只读属性 readonly

函数

  • 接口中定义函数
  • 函数定义参数和返回值类型(参数可选,没有返回可用用 void 表示 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Person {
age: number,
readonly name: string,
amount?: number,

// any 任意类型 联合类型 string|number 范围为其中一种
[proName: string]: any,

eat(name: string): string
}

const ada: Person =
{
age: 25,
name: "阿达",
friends: "萨瓦迪卡",
eat(name: string): string {
return `${name}在吃东西`
}
}

console.log(ada.eat("阿达"));

function函数

extends

  • 接口的继承关系
  • 支持多继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface A {
name: string
}

interface B {
age: number
}

// 多继承 P 拥有 A B 的属性和自己定义的属性
interface P extends A, B {
amount: number
}

// 初始化对象必须要实现 P 拥有的属性
let ada: P = {
name: "阿达",
age: 88,
amount: 666
}
console.log(ada)

Array

  • Array 数组

常用声明方式

  • 一般可以通过 类型[] Array<具体类型> 两种方式来进行声明
  • 一般用泛型声明,泛型的类型为 any 是最常用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义方式一  类型[]
let arrStr: string[] = ['我', '是', '阿', '达'];
let arrNum: number[] = [1, 3, 5, 7, 9];
let arrBoolean: boolean[] = [true, false, true];
let arrAny: any[] = ['我', 1, true]

// 定义方式二 泛型 Array<具体类型>
let arrStr1: Array<string> = ['我', '是', '阿', '达1'];
let arrNum1: Array<number> = [1, 3, 5, 7, 91];
let arrBoolean1: Array<boolean> = [true, false, true, false];
let arrAny1: Array<any> = ['我', 1, true, 1];

console.log(arrStr)
console.log(arrStr1)
console.log(arrNum)
console.log(arrNum1)
console.log(arrBoolean)
console.log(arrBoolean1)
console.log(arrAny)
console.log(arrAny1)

声明数组

多维数组

  • 数组中的值还是一个数组
  • 和基本定义的一样也有两种方式

多维数组

arguments

  • arguments 是入参的集合,该对象需要用 IArguments 类来接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// arguments 参数集合

function add(...args: any): void {
console.log(arguments)
// 我们传入的参数全部都是数字但是我们不能通过 number[] 或者 any[] 来进行接收
// let arr1: number[] = arguments;
// let arr2: any[] = arguments;

// 只能用 IArguments 接收,从右侧控制台打印也能看出数据的类型
let arr3: IArguments = arguments;
console.log(arr3)
}

add(1, 2, 3)

arguments

类数组

  • 一般用来描述类数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 通过接口定义的数组一般用来描述 类数组

interface ArrNumber {
// index 下标用 number 类型表示 值的类型约束为 number
[index: number]: number
}

interface ArrString {
// index 下标用 number 类型表示 值的类型约束为 string
[index: number]: string
}

let arrNumber: ArrNumber = [1, 2, 3, 5, 7]
let arrString: ArrString = ['1', '2', '3', '5', '7']

console.log(arrNumber)
console.log(arrString)

类数组

函数扩展

基础使用

  • 默认情况 参数不能多传,也不能少传 必须按照约定的类型来
  • 可通过 ? 号将参数标记为可选参数,例如 amount?: number 在使用该函数的时候被标记的参数就可传可不传了(如果不传的话,再去使用该参数就是 undefined)
  • 也可在声明参数的时候给默认值,例如 amount: number = 0 如果不传该参数就会使用默认值,传了就会以实际传参为准
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 基础使用

//ES6 方法体只有一条语句 不用大括号和 return
const fun = (age: number, name: string, amount: number): string => `age=${age},name=${name},amount=${amount}`;
let ada = fun(18, 'ada', 10000)
console.log(ada)

// amount --> 指定为可选参数,可传可不传(但是这样输出的时候会是 amount=undefined )
const fun1 = (age: number, name: string, amount?: number): string => `age=${age},name=${name},amount=${amount}`;
let ada1 = fun1(18, 'ada')
console.log(ada1)

// 默认参数 --> 如果 amount 不传就默认为 0 ,传了就以传的为准
const fun2 = (age: number, name: string, amount: number = 0): string => `age=${age},name=${name},amount=${amount}`;
let ada2 = fun2(18, 'ada')
let ada3 = fun2(18, 'ada', 3000)

console.log(ada2)
console.log(ada3)

基础使用

接口约束函数

  • 通过接口定义一个类型,函数约定为该类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 同样支持可选参数
interface User {
name: string,
age: number,
amount?: number
}

// 定义了一个函数叫做 printUser,传入的参数类型为 User 类型,返回值也为 User 类型
const printUser = (user: User): User => {
return user
}
// 传参需要满足约定类型 User 类型的对象 {}
console.log(printUser(
{
name: "阿达",
age: 19,
}
)
)

接口约束函数

函数的重载

  • 函数名相同,参数列表不同
  • 参数列表包括参数的类型和个数以及参数的顺序
  • 与返回类型无关
  • 如果参数类型不同,则参数类型应设置为 any
  • 参数数量不同你可以将不同的参数设置为可选
1
2
3
4
5
6
7
8
9
10
// 上面两个是函数的重载
function fun(params: string): string;
function fun(params: string, params2: number): any;
//这个是执行函数
function fun(params: string, params2?: number): string {
return params + params2
}

console.log(fun('1'));
console.log(fun('我', 123));

重载函数

剩余参数(展开)

  • 可变参数用 …args 来表示
  • 在定义可变参数时,也可以定义一些固定参数,然后用可变参数定义剩余参数
1
2
3
4
5
6
7
8
const fn = (array:number[],...items:any[]):any[] => {
console.log(array,items)
return items
}

let a:number[] = [1,2,3]

fn(a,'4','5','6')

联合类型

  • 联合类型,能约束属性为多个类型中的一个
  • 比如工作中需求瞬息万变,一个联系人的电话既可以存 11 位手机号,又可以存座机号或者其它场景要求 既可以传参 0或1,又可以传参布尔值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 联合类型,能约束属性为多个类型中的一个
let phone: number | string = '021-09xxxx98'
let phone2: number | string = 13566778899

console.log(phone.length)
// phone2 因为实际类型为 number 类型,因此没有 length 属性
// console.log(phone2.length)
console.log(phone2)

let fn = (beautiful: number | boolean): boolean => {
//强转 2 次 0 --> true --> false true --> false --> true
return !!beautiful;
}
//非 0 及真
console.log(fn(0));
console.log(fn(true));

联合类型

交叉类型

  • 多种类型的集合,联合对象将具有所联合类型的所有成员
  • 如下示例 info 参数为 Person Man 的交叉类型,传递参数时需要传递两个类型的所有必填参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 交叉类型 有点类是与 extends
interface Person {
name: string
age: number
}

interface Man {
hasBeard: boolean
}

let ada = (info: Person & Man): void => {
console.log(info)
}

ada({name: "阿达", age: 18, hasBeard: true})

交叉类型

类型断言

  • 需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误
  • 语法: 值 as 类型 value as string<类型>值 <string>value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 类型断言
let fun = (amount: number | string): void => {
console.log((amount as string).length)
}

fun("10元")
fun(10)

interface F1 {
run: string
}

interface F2 {
build: string
}

let fun2 = (F: F1 | F2): void => {
console.log((<F2>F).build)
}
fun2({build: "bui"})
fun2({run: "bui"})

类型断言


  • 使用 any 临时断言
1
2
3
4
5
//这样写会报错因为 window 对象没有abc这个东西
window.abc = 123

//可以使用any临时断言在 any 类型的变量上,访问任何属性都是允许的。
(window as any).abc = 123
  • 断言型断言是不具影响力的
  • 在下面的例子中,将 something 断言为 boolean 虽然可以通过编译,但是并没有什么用 并不会影响结果, 因为编译过程中会删除类型断言
1
2
3
4
5
6
function toBoolean(something: any): boolean {
return something as boolean;
}
// 返回值为 1 并不会因为方法中进行了断言 或者定义了返回值类型这里就变成了布尔值
toBoolean(1);

内置对象

ECMAScript 内置对象

  • BooleanNumberStringRegExpDateError
1
2
3
4
5
6
7
8
9
10
11
12
let b: Boolean = new Boolean(1)
console.log(b)
let n: Number = new Number(true)
console.log(n)
let s: String = new String('哔哩哔哩XXXX')
console.log(s)
let d: Date = new Date()
console.log(d)
let r: RegExp = /^1/
console.log(r)
let e: Error = new Error("error!")
console.log(e)

DOM 内置对象

  • **DocumentHTMLElementNodeList **
1
2
3
4
5
6
7
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
//读取div 这种需要类型断言 或者加个判断应为读不到返回null
let div: HTMLElement = document.querySelector('div') as HTMLDivElement
document.addEventListener('click', function (e: MouseEvent) {

});

BOM 内置对象

  • Event
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//dom元素的映射表
interface HTMLElementTagNameMap {
"a": HTMLAnchorElement;
"abbr": HTMLElement;
"address": HTMLElement;
"applet": HTMLAppletElement;
"area": HTMLAreaElement;
"article": HTMLElement;
"aside": HTMLElement;
"audio": HTMLAudioElement;
"b": HTMLElement;
"base": HTMLBaseElement;
"bdi": HTMLElement;
"bdo": HTMLElement;
"blockquote": HTMLQuoteElement;
"body": HTMLBodyElement;
"br": HTMLBRElement;
"button": HTMLButtonElement;
"canvas": HTMLCanvasElement;
"caption": HTMLTableCaptionElement;
"cite": HTMLElement;
"code": HTMLElement;
"col": HTMLTableColElement;
"colgroup": HTMLTableColElement;
"data": HTMLDataElement;
"datalist": HTMLDataListElement;
"dd": HTMLElement;
"del": HTMLModElement;
"details": HTMLDetailsElement;
"dfn": HTMLElement;
"dialog": HTMLDialogElement;
"dir": HTMLDirectoryElement;
"div": HTMLDivElement;
"dl": HTMLDListElement;
"dt": HTMLElement;
"em": HTMLElement;
"embed": HTMLEmbedElement;
"fieldset": HTMLFieldSetElement;
"figcaption": HTMLElement;
"figure": HTMLElement;
"font": HTMLFontElement;
"footer": HTMLElement;
"form": HTMLFormElement;
"frame": HTMLFrameElement;
"frameset": HTMLFrameSetElement;
"h1": HTMLHeadingElement;
"h2": HTMLHeadingElement;
"h3": HTMLHeadingElement;
"h4": HTMLHeadingElement;
"h5": HTMLHeadingElement;
"h6": HTMLHeadingElement;
"head": HTMLHeadElement;
"header": HTMLElement;
"hgroup": HTMLElement;
"hr": HTMLHRElement;
"html": HTMLHtmlElement;
"i": HTMLElement;
"iframe": HTMLIFrameElement;
"img": HTMLImageElement;
"input": HTMLInputElement;
"ins": HTMLModElement;
"kbd": HTMLElement;
"label": HTMLLabelElement;
"legend": HTMLLegendElement;
"li": HTMLLIElement;
"link": HTMLLinkElement;
"main": HTMLElement;
"map": HTMLMapElement;
"mark": HTMLElement;
"marquee": HTMLMarqueeElement;
"menu": HTMLMenuElement;
"meta": HTMLMetaElement;
"meter": HTMLMeterElement;
"nav": HTMLElement;
"noscript": HTMLElement;
"object": HTMLObjectElement;
"ol": HTMLOListElement;
"optgroup": HTMLOptGroupElement;
"option": HTMLOptionElement;
"output": HTMLOutputElement;
"p": HTMLParagraphElement;
"param": HTMLParamElement;
"picture": HTMLPictureElement;
"pre": HTMLPreElement;
"progress": HTMLProgressElement;
"q": HTMLQuoteElement;
"rp": HTMLElement;
"rt": HTMLElement;
"ruby": HTMLElement;
"s": HTMLElement;
"samp": HTMLElement;
"script": HTMLScriptElement;
"section": HTMLElement;
"select": HTMLSelectElement;
"slot": HTMLSlotElement;
"small": HTMLElement;
"source": HTMLSourceElement;
"span": HTMLSpanElement;
"strong": HTMLElement;
"style": HTMLStyleElement;
"sub": HTMLElement;
"summary": HTMLElement;
"sup": HTMLElement;
"table": HTMLTableElement;
"tbody": HTMLTableSectionElement;
"td": HTMLTableDataCellElement;
"template": HTMLTemplateElement;
"textarea": HTMLTextAreaElement;
"tfoot": HTMLTableSectionElement;
"th": HTMLTableHeaderCellElement;
"thead": HTMLTableSectionElement;
"time": HTMLTimeElement;
"title": HTMLTitleElement;
"tr": HTMLTableRowElement;
"track": HTMLTrackElement;
"u": HTMLElement;
"ul": HTMLUListElement;
"var": HTMLElement;
"video": HTMLVideoElement;
"wbr": HTMLElement;
}

Promise

  • 定义 Promise 时,如果我们不指定返回的类型TS是推断不出来返回的是什么类型
1
2
3
4
5
6
7
8
9
10
// 函数和 new 对象的时候都需要指定泛型  这个 Promise 才知道要返回什么类型
function promise(): Promise<number> {
return new Promise<number>((resolve, reject) => {
resolve(1)
})
}

promise().then(res => {
console.log(res)
})

Class

类的基本声明

  • 在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明
  • 如果了定义了属性但是构造函数不进行初始化 也会报错 通常是给个默认值(在定义属性的时候给个默认值) 或者 进行赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
//定义三个属性 和一个带有默认值的属性(如果gender 不进行默认值赋值,就需要在构造器中初始化,不然会报错)
name: string
age: number
amount: number | string
gender: string = "未知"

//constructor 构造器
constructor(name: string, age: number, amount: number | string) {
this.name = name;
this.age = age;
this.amount = amount;
}
}

let ada = new Person("ada", 18, "10元");
let ada2 = new Person("阿达", 20, 100);

console.log(ada, ada2)

定义类

修饰符

  • TS 中类属性有三个权限修饰符 public private protected
  • 属性显示声明的情况下默认就是 public
  • public 没有限制作用域,类外也能访问
  • private 只能在类的内部访问
  • protected 内部和子类中能访问
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
class Person {
//姓名大家都可以知道公开的,年龄的话属于一点点隐私只有比较熟的人能访问,个人余额是保密的只有自己能访问
public name: string
protected age: number
private amount: number | string
gender: string = "未知"

//constructor 构造器
constructor(name: string, age: number, amount: number | string) {
this.name = name;
this.age = age;
this.amount = amount;
}
}

let ada = new Person("ada", 18, "10元");
// 外部只能访问 public 属性(gender 未显示声明,默认为 public)
console.log(ada.name, ada.gender)


class Man extends Person {
constructor(name: string, age: number, amount: number | string) {
super(name, age, amount);
// amount 属性子类中也不能访问
console.log(this.age, this.name, this.gender);
}
}

new Man("TypeScript", 5, 300)

修饰符

static 修饰符

  • 静态方法只能访问静态属性(和 java 差不多)
  • 类中的静态函数之间可以用 this 相互调用
  • 非静态函数中如果要调用静态函数需要用 类名.静态函数名
  • 外部调用静态函数也是用 类名.静态函数名 不需要再单独 New 对象了
  • static 属性在构造器中也只能用 类名.静态属性名
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
// 静态属性  静态函数
class Person {
public name: string
protected age: number = 23
private static amount: number | string = 0

//constructor 构造器
constructor(name: string, age: number) {
this.name = name;
this.age = age;
console.log("初始化对象")
//非静态函数中如果要调用静态函数需要用 类名.静态函数名
console.log(Person.getPerson());
}

// 静态函数中只能访问静态属性
static getAmount() {
return this.amount
}

static getPerson() {
//静态函数调用另外一个静态函数,直接用 this
return this.getAmount()
}
}

//外部调用静态函数也是用 类名.静态函数名 不需要再单独 New 对象了
console.log(Person.getAmount());
new Person("ada", 20)

static

interface

  • interface 定义类 使用关键字 implements 后面跟interface的名字多个用逗号隔开 继承还是用extends
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
interface Person {
get(number: number): string
}

interface Man {
say(): string
}

class Woman {
eat(): boolean {
return true
}
}

//继承 Woman 并且实现 Person, Man 两个接口
class User extends Woman implements Person, Man {
get(number: number): string {
if (number === 1) {
return "---子类实现接口未显示的方法";
} else {
return "+++子类实现接口未显示的方法";
}
};

say(): string {
return "Hello, world!";
}

}

// 创建对象后调用实现的方法和继承方法
let user = new User;
console.log(user.get(5));
console.log(user.say());
console.log(user.eat());

接口

abstract

  • 应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类
  • 或者你也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法
  • 抽象类中可以有普通函数和抽象函数
  • 普通函数可以实现具体方法,派生类不需要实现该方法
  • 抽象函数不能实现具体业务,派生类必须要实现该方法
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
// 抽象类中可以有普通函数和抽象函数
abstract class Abs {
name: string

protected constructor(name: string) {
this.name = name;
}

// 普通函数可以实现具体方法,派生类不需要实现该方法
setName(name: string): void {
this.name = name
}

// 抽象函数不能实现具体业务,派生类必须要实现该方法
abstract getName(): string

}

class PersonAbs extends Abs {
constructor() {
super("ada");
}

getName(): string {
return this.name;
}

}

let p = new PersonAbs()
console.log(p.getName());
p.setName("setName")
console.log(p.getName());

abstract

元组Tuple

  • 元组就是数组的变种
  • 元组 Tuple 是固定数量的不同类型的元素的组合(但是也可以越界 push)
  • 元组与集合的不同之处在于,元组中的元素类型可以是不同的,而且数量固定。元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 元组 赋值的类型和长度需要和定义时一致
let arrTuple: [string, number] = ["1", 1]
let arrTuple2: [string, number, boolean] = ["1", 1, false]

console.log(arrTuple[0].length);
// arrTuple[1] 自动推断出来是数字类型所以没有 length 属性
// console.log(arrTuple[1].length);

console.log(arrTuple)
// 越界元组(上面显示声明的时候 arrTuple 长度只有 2),但是这里可以 越界 push 类型约束为联合类型
arrTuple.push(2, 'ada')
console.log(arrTuple)


// 实际应用场景比如 excel 对象
let excel: [string, string, number, string][] = [
['title1', 'name1', 1, '123'],
['title2', 'name2', 2, '123'],
['title3', 'name3', 3, '123'],
['title4', 'name4', 4, '123'],
['title5', 'name5', 5, '123'],
]

元组 Tuple

enum

  • 在 JavaScript 中是没有枚举的概念的 TS 帮我们定义了枚举这个类型

数字枚举

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
// 数字枚举 只需要定义属性名,会自动给你排序下标
enum Color {
RED,
GREEN,
BLUE,
}

console.log(Color.RED, Color.GREEN, Color.BLUE)

//增长枚举,给第一个枚举对象定义下标,后面的会自动增长
enum Color2 {
RED = 3,
GREEN,
BLUE,
}

console.log(Color2.RED, Color2.GREEN, Color2.BLUE)

//自定义枚举,没有自定义的会根据前面一个的值自动增长
enum Color3 {
RED = 1,
GREEN = 5,
BLUE,
}

console.log(Color3.RED, Color3.GREEN, Color3.BLUE)

数字枚举

字符串枚举

  • 字符串枚举的概念很简单。 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
  • 由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 - 它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字
  • 下面的 Color1 其实算是一个异构枚举(这里只是为了对比)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 字符串枚举 没有自增的说法定义了一个,后面的都要定义()
enum Color {
RED = "red",
GREEN = "green",
BLUE = "blue",
}

console.log(Color.RED, Color.GREEN, Color.BLUE)

// 字符串枚举,第一个不定义字符串默认为 0,第二个定义了字符串后面的都要定义字符串
enum Color1 {
RED,
GREEN = "green",
BLUE = "blue",
}

console.log(Color1.RED, Color1.GREEN, Color1.BLUE)

字符串枚举

异构枚举

  • 将枚举的类型进行混合为字符串和数字类型
1
2
3
4
5
6
7
8
9
// 异构枚举 不到万不得不用这种
enum bool {
YES = 'true',
NO = 0,
UNKNOWN
}

// unknown 在 no 下,因为 no 定义了 0 ,name unknown 会自增
console.log(bool.YES, bool.NO, bool.UNKNOWN)

异构枚举

接口枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum Color {
RED = 1,
GREEN,
BLUE,
}

// 接口属性的值为枚举属性
interface Col {
red: Color.RED
}

// 要实现接口中的属性,值的类型必须要和枚举中值的类型相同,因为枚举中声明了 RED = 1 ,那么这里赋值的时候必须是 number 型
let C: Col = {
red: 4
}

let C2: Col = {
red: Color.RED
}
console.log(C.red)
console.log(C2.red)

接口枚举

const枚举

  • 什么枚举 let 和 var 都是不允许的声明只能使用const 或者不用
  • 大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义
  • const 声明的枚举会被编译成常量,普通声明的枚举编译完后是个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const enum bool {
YES = 'true',
NO = 0,
}

if (bool.NO === 0) {
}

//-----------对比----------------

enum bool2 {
YES = 'true',
NO = 0,
}

if (bool2.NO === 0) {
}

const枚举

反向映射

  • 它包含了正向映射( name -> value)和反向映射( value -> name
  • 要注意的是 不会为字符串枚举成员生成反向映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 反向映射
enum Type {
SUCCESS = 'true',
FAIL = 0
}

let SUCCESS = Type.SUCCESS
let failureValue = Type.FAIL

console.log(SUCCESS, failureValue)

// 这里能取 FAIL 的 key ,SUCCESS 是字符串不能取到 Key
let failKeys = Type[failureValue]
console.log(failKeys)

反向映射

类型推论&类型别名

  • 什么是类型推论?—> 我声明了一个变量,在没有约束类型的情况对其进行了赋值,那么 TS 就会根据赋值的类型推断出实际的类型
  • 如果你声明变量没有定义类型也没有赋值这时候 TS 会推断成 any 类型可以进行任何操作
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
// 类型推论  没有约束类型  a:string b:number
let a = 'str'
let b = 123

// 声明时没有定义值,这个时候就是 any 类型
let an
an = "123"
an = 123
an = false
an = [1, "22", false]

console.log(a, b, an)

// 类型别名 s 等同与 string 类型
type s = string
let username: s = "ada"
// 类型别名 联合类型
type snb = string | number | boolean
let amount: snb = 0
amount = "0元"
amount = false
console.log(username, amount)

// 函数式类型别名,str 类型的函数必须返回 string
type str = () => string
const getName: str = () => "ada函数式别名"
console.log(getName());

// 字面量类型别名 变量 s 的赋值 只能是 status 类型值中的 on 或者 off false 三个中的一个
type status = "on" | "off" | false
let s: status = "on"
console.log(s)

类型推论&类型别名

never 类型

  • TypeScript 将使用 never 类型来表示不应该存在的状态
  • never 与 void 差异: ① void 类型只是没有返回值但本身不会出错 ② never 只会抛出异常没有返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// never 类型 这里约束的是一个交叉类型 实际上是一个不可能的类型,所以被推断出了 never 类型
let never1: string & number

// 返回never的函数必须存在无法达到的终点
// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
throw new Error(message);
}

// 因为存在死循环,所以 loop 将不会有返回值
function loop(): never {
while (true) {
}
}
  • never 类型的一个应用场景:现在有 Working Happy 两个接口,产品加了一个新需求,刚好老同事离职了,让新同事加个 Money 接口,我们必须手动找到所有 switch 代码并处理,否则将有可能引入 BUG ,而且这将是一个“隐蔽型”的BUG,如果回归面不够广,很难发现此类BUG,那 TS 有没有办法帮助我们在类型检查阶段发现这个问题呢?当然是有的,由于任何类型都不能赋值给 never 类型的变量,所以当存在进入 default 分支的可能性时,TS的类型检查会及时帮我们发现这个问题
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
interface Working {
today: "工作日"
}

interface Happy {
today: "休息日"
}

// 1.产品新加一个需求,新同时把这个 interface 加上了
interface Money {
today: "今天 10 号发工资"
}

// 2.类型别名也加上了
type days = Working | Happy | Money

function f(day: days) {
switch (day.today) {
// 3.这个 case 的代码他不清楚就没加
case "工作日":
console.log("搬砖")
break
case "休息日":
console.log("看电影")
break
// 兜底逻辑
default:
// 4.这里编译就会报错,这样就能防止出现其它意外,这就是 never 的一个经典场景
const check: never = day
break
}
}

never经典应用场景

Symbol

  • Symbol 是ES6 新增的一个类型,意寓着象征的意思
  • symbol类型的值是通过Symbol构造函数创建的。
  • 可以传递参做为唯一标识 只支持 string 和 number类型的参数,其它类型的值会被 toString

基础使用

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
let str = Symbol('字符串')
let num = Symbol(123)

console.log(str, num)

let obj = {
[str]: "stri",
[num]: 18,
name: "ada",
arg: "25"
}
//for in 遍历的时候 不会输出 Symbol 类型的 key
for (let key in obj) {
console.log(key)
}

// Object.keys Object.getOwnPropertyNames 遍历的时候 不会输出 Symbol 类型的 key
console.log(Object.keys(obj))
console.log(Object.getOwnPropertyNames(obj))
// 转成 JSON Symbol 类型的 不会被转进去
console.log(JSON.stringify(obj));

// Object.getOwnPropertySymbols 或者 Symbol 类型的属性
console.log(Object.getOwnPropertySymbols(obj))
// 获取所有的属性
console.log(Reflect.ownKeys(obj))

Symnol基础使用

Iterator迭代器

  • 迭代器不支持对象(对象没有Symbol.iterator() 方法) Map Set Array 这些都支持
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
// Iterator
// 定义一个 number 类型的数组
let arr: Array<number> = [1, 3, 5]
// 通过 Symbol.iterator 的方式迭代 arr 返回的类型是 Iterator<number> ,泛型指定为 arr 的泛型
let numbers: Iterator<number> = arr[Symbol.iterator]();
// next 方法有会返回一个对象,包含两个属性一个是 value 表示迭代出来的值,另外一个是 done 表示是否结束迭代( [true/false])
console.log(numbers.next());
console.log(numbers.next());
console.log(numbers.next());
// 其实只能迭代三次第四次的 value= undefined done=true (表示没有了)
console.log(numbers.next());

// 迭代器示例done 的属性值取反
let arr2: Array<number> = [1, 3, 5]
let set: Set<string> = new Set<string>(["我", "是", "ada"]);
let map: Map<string, any> = new Map<string, any>()
map.set("name", "ada")
map.set("age", 20)

let iterator = (args: any): void => {
let it: Iterator<any> = args[Symbol.iterator]()
let next: any = {done: false}
while (!next.done) {
next = it.next()
if (!next.done) {
console.log(next.value);
}
}
}

iterator(arr2)
iterator(set)
iterator(map)

iterator迭代器

for of

  • for of 就像是 iterator 迭代器的语法糖,底层会自动调用Symbol.iterator() 方法帮我们进行迭代
  • 同样也不支持对象
  • 与 for in 的区别: for in 主要针对与 Array 迭代出来的是索引,for of 支持遍历大部分类型迭代器 arr nodeList argumetns set map 等,迭代出来的是 value
  • ps:截图中我的 let item of items 编辑器在报错,但是也能正常运行不知道咋回事
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// for of 生成器
let arr: Array<number> = [1, 3, 5]
let set: Set<string> = new Set<string>(["我", "是", "ada"]);
let map: Map<string, any> = new Map()
map.set("name", "ada")
map.set("age", 20)

type asm = Array<number> | Set<string> | Map<string, any>
let forOf = (items: asm): void => {
for (let item of items) {
console.log(item)
}
console.log("------------------------")
}

forOf(arr)
forOf(set)
forOf(map)

for of生成器

其它函数

  • Symbol.hasInstance 方法,会被instanceof运算符调用。构造器对象用来识别一个对象是否是其实例。
  • Symbol.isConcatSpreadable布尔值,表示当在一个对象上调用Array.prototype.concat时,这个对象的数组元素是否可展开。
  • Symbol.iterator 方法,被for-of语句调用。返回对象的默认迭代器。
  • Symbol.match 方法,被String.prototype.match调用。正则表达式用来匹配字符串。
  • Symbol.replace 方法,被String.prototype.replace调用。正则表达式用来替换字符串中匹配的子串。
  • Symbol.search 方法,被String.prototype.search调用。正则表达式返回被匹配部分在字符串中的索引。
  • Symbol.species 函数值,为一个构造函数。用来创建派生对象。
  • Symbol.split 方法,被String.prototype.split调用。正则表达式来用分割字符串。
  • Symbol.toPrimitive 方法,被ToPrimitive抽象操作调用。把对象转换为相应的原始值。
  • Symbol.toStringTag 方法,被内置方法Object.prototype.toString调用。返回创建对象时默认的字符串描述。
  • Symbol.unscopables 对象,它自己拥有的属性会被with作用域排除在外。

泛型

  • 泛型在 TS 中是很重要的东西,例如 VUE3 是用 TS 编写的,里面用得到了非常多的泛型

泛型函数

  • 在我们的场景中会有很多功能一样的函数,但是需要的参数类型却是各不相同,如下两端代码,功能一致,但是对传参类型一个是 number,另外一个是 string
1
2
3
4
5
6
7
8
9
10
11
function num(a: number, b: number): Array<number> {
return [a, b];
}

num(15, 20)

function str(a: string, b: string): Array<string> {
return [a, b];
}

str("好好学", "上网")
  • 泛型函数语法为函数名字后面跟一个<参数名> 参数名可以随便写 例如我这儿写了 T
  • 当我们使用这个函数的时候把参数的类型传进去就可以了(也就是动态类型)
  • 我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function returnInput<T>(a: T, b: T): Array<T> {
return [a, b]
}

// 调用泛型函数时可做泛型类型约束,如 returnInput<number>(15, 20) ,或不约束 returnInput(15, 20) 会自动触发类型推论
console.log(returnInput<number>(15, 20));
console.log(returnInput(15, 20));
console.log(returnInput("好好学", "上网"));

// 多个类型
function returnInput2<N, S>(a: N, b: S): Array<N | S> {
return [a, b]
}

console.log(returnInput2(5, "幼儿园"));

泛型函数

泛型约束

  • 上面我们已经实现了泛型化了,但是这种泛型太任意了,可能会导致传递的参数类型与我们预期的不一致,因此我们可以对泛型进行一定的约束 使用 extends 关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
interface len {
length: number
}

// 定义了一个泛型方法,要求传递的参数必须要有 length 属性,且属性值是 number 类型
function f<T extends len>(a: T): number {
return a.length
}

// 调用 f 方法是要求传递的参数必须要有 length 属性,如果传如的是 boolean number 是没有 length 属性的
console.log(f("123"));
console.log(f([1, 2, 4, "China"]));
console.log(f({length: 10, age: 19}));

泛型约束

keyof

  • 其中使用了TS泛型和泛型约束。首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// K extends keyof O  --->  K 类型为 O 类型的所有 KEY 的集合
function prop<O, K extends keyof O>(obj: O, key: K) {
// 根据 key值, 获取 obj 该 key 对应的 value
return obj[key];
}

enum Gender {
WOMAN = 0,
MAN = 1,
OTHER = 2
}

let user = {
name: "阿达",
age: 18,
grand: Gender.MAN
}
// 传递的第一个参数是任意类型,但是第二个参数必须要是第一个参数的属性中的 任意一个 key
console.log(prop(user, "name"));
console.log(prop(user, "age"));
console.log(prop("abc", "length"));

keyof

泛型类

  • 声明方法跟函数类似名称后面定义<类型>
  • 使用的时候确定类型new Sub<类型>()
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
class Clazz<S, N> {

str: S
num: N

constructor(str: S, num: N) {
this.str = str
this.num = num
}

arr(a: S, b: N): Array<S | N> {
return [a, b]
}

getStr(a: S): S {
return a
}

getNum(b: N, c: N): N[] {
return [b, c]
}
}

// 先约束一下类型
let cla = new Clazz<string, number>("string", 0)
cla.str = "string 类型"
cla.num = 10

console.log(cla.arr("1", 2));
console.log(cla.getStr("参数只能传 S 类型的"));
console.log(cla.getNum(1, 2));

泛型类