TypeScript快速入门

TypeScript 是一种由微软开发的自由和开源的编程语言。

它是 JavaScript 的一个超集,本质是向这个语言添加可选的静态类型和基于类的面向对象编程。

image-20220927213921669

使用TypeScript 编译器

安装TypeScript

1
npm install -g typescript

编译 TypeScript 文件

1
2
3
4
tsc hello.ts
# hello.ts => hello.js

node hello.js

对于刚入门 TypeScript 的小伙伴,也可以不用安装 typescript

直接使用线上的 TypeScript Playground 来学习新的语法或新特性。

编译完成后,目录中会出现hello.js,其中内容与ts中的一模一样,然后node hello.js就会进行输出。

细心的同学,可能会发现。当函数名或者是变量名相同的时候,会提示重复定义的问题,此时就应该优化编译方法

优化编译

  1. 解决TS和JS变量冲突问题,会生成tsconfig.json配置文件

    1
    tsc --init  #生成配置文件
  2. 自动编译,生成dist文件夹,里面存放着编译好的js文件

    1
    tsc --watch
  3. 发出错误

    1
    tsc --noEmitOnError hello.ts
  4. nodejs环境执行ts

    1
    2
    npm i @types/node --save-dev // node环境支持的依赖必装
    npm i ts-node --g

降级编译

降级ES6语法,适配浏览器。

1
2
tsconfig.json:
"target":"es5",

严格模式

1
2
3
4
tsconfig.json:
"strict":true,
"noImplicitAny":true,
"strictNullChecks":true,

基元类型

基础类型:Boolean、Number、String、null、undefined 以及 ES6 的 Symbol 和 ES10 的 BigInt。

字符串类型(string)

字符串是使用string定义的

1
2
3
4
//普通声明
let a: string = '123'
//也可以使用es6的字符串模板
let str: string = `dddd${a}`

数字类型(number)

支持十六进制、十进制、八进制和二进制

1
2
3
4
5
6
7
let notANumber: number = NaN;//Nan
let num: number = 123;//普通数字
let infinityNumber: number = Infinity;//无穷大
let decimal: number = 6;//十进制
let hex: number = 0xf00d;//十六进制
let binary: number = 0b1010;//二进制
let octal: number = 0o744;//八进制s

布尔类型(boolean)

注意,使用构造函数 Boolean 创造的对象不是布尔值

1
2
let createdBoolean: boolean = new Boolean(1)
//这样会报错 事实上 new Boolean() 返回的是一个 Boolean 对象

事实上 new Boolean() 返回的是一个 Boolean 对象 (valueOf可以读值)需要改成

1
2
let booleand: boolean = true //可以直接使用布尔值
let booleand2: boolean = Boolean(1) //也可以通过函数返回布尔值

空值类型(void)

JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数

1
2
3
function voidFn(): void {
console.log('test void')
}

void 类型的用法,主要是用在我们不希望调用者关心函数返回值的情况下,比如通常的异步回调函数

void也可以定义undefined 和 null类型

1
2
let u: void = undefined
let n: void = null;

Null和undefined类型

1
2
let u: undefined = undefined;//定义undefined
let n: null = null;//定义null

voidundefinednull 最大的区别

与 void 的区别是,undefined 和 null 是所有类型的子类型

也就是说 undefined 类型的变量,可以赋值给 string 类型的变量:

1
2
3
4
5
//这样写会报错 void类型不可以分给其他类型
let test: void = undefined
let num2: string = "1"

num2 = test
1
2
3
4
5
6
7
8
9
10
11
//这样是没问题的
let test: null = null
let num2: string = "1"

num2 = test

//或者这样的
let test: undefined = undefined
let num2: string = "1"

num2 = test

注意,如果你配置了tsconfig.json 开启了严格模式

那么要注意null不能赋予void类型

任意类型

Any 类型 和 unknown 顶级类型

  1. 没有强制限定哪种类型,随时切换类型都可以 我们可以对 any 进行任何操作,不需要检查类型

    1
    2
    3
    let anys:any = 123
    anys = '123'
    anys = true
  2. 声明变量的时候没有指定任意类型默认为any

    1
    2
    3
    let anys;
    anys = '123'
    anys = true

缺点

  1. 如果使用any 就失去了TS类型检测的作用

  2. TypeScript 3.0中引入的 unknown 类型也被认为是 top type ,但它更安全。

    与 any 类型一样,所有类型都可以分配给unknown

    unknow类型比any类型更加严格,当你要使用any 的时候可以尝试使用unknow

区别

注意:

  • unknow类型不能作为子类型只能作为父类型,any可以作为父类型和子类型
  • unknown类型不能赋值给其他类型,any类型可以
  • 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
//unknown 可以定义任何类型的值
let value: unknown;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = null; // OK
value = undefined; // OK
value = Symbol("type"); // OK

//这样写会报错unknow类型不能作为子类型只能作为父类型 any可以作为父类型和子类型
//unknown类型不能赋值给其他类型
let names:unknown = '123'
let names2:string = names

//这样就没问题 any类型是可以的
let names:any = '123'
let names2:string = names

//unknown可赋值对象只有unknown 和 any
let bbb:unknown = '123'
let aaa:any= '456'

aaa = bbb
  • 如果是any类型在对象没有这个属性的时候还在获取是不会报错的

  • 如果是unknow 是不能调用属性和方法

1
2
3
4
5
6
7
8
// 如果是any类型在对象没有这个属性的时候获取是不会报错的
let obj:any = {b:1}
obj.a

// 如果是unknow 则不能调用属性和方法
let obj:unknown = {b:1,ccc:():number=>213}
obj.b
obj.ccc()

接口和对象类型

对象类型

在typescript中,我们定义对象的方式要用关键字interface(接口),定义方式如下:

大佬的理解是使用interface来定义一种约束,让数据结构满足约束的格式

1
2
3
4
5
6
7
8
9
10
11
//这样写是会报错的 因为我们在person定义了a,b但是对象里面缺少b属性
//使用接口约束的时候不能多一个属性也不能少一个属性
//必须与接口保持一致
interface Person {
b:string,
a:string
}

const person:Person = {
a:"213"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//重名interface会合并
interface A{name:string}
interface A{age:number}
var x:A={name:'xx',age:20}

//继承
interface A{
name:string
}

interface B extends A{
age:number
}

let obj:B = {
age:18,
name:"string"
}

可选属性 使用 ? 操作符

1
2
3
4
5
6
7
8
9
10
//可选属性的含义是该属性可以不存在
//所以说这样写也是没问题的
interface Person {
b?:string,
a:string
}

const person:Person = {
a:"213"
}

任意属性 [propName: string]

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

1
2
3
4
5
6
7
8
9
10
11
12
13
//在这个例子当中我们看到接口中并没有定义C但是并没有报错
//因为我们定义了[propName: string]: any;
//允许添加新的任意属性
interface Person {
b?:string,
a:string,
[propName: string]: any;
}

const person:Person = {
a:"213",
c:"123"
}

只读属性 readonly

readonly 只读属性是不允许被赋值,只能读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//这样写会报错
//因为a是只读的不允许重新赋值
interface Person {
b?: string,
readonly a: string, // 在这里
[propName: string]: any;
}

const person: Person = {
a: "213",
c: "123"
}

person.a = 123

添加函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
b?: string,
readonly a: string,
[propName: string]: any;
cb():void
}

const person: Person = {
a: "213",
c: "123",
cb:()=>{
console.log(123)
}
}

数组类型

类型[ ]

注意不能使用数组的操作方法进行添加,例如unshift()

1
2
3
4
5
6
7
8
9
10
11
12
13
//类型加中括号
let arr:number[] = [123]
//这样会报错定义了数字类型出现字符串是不允许的
let arr:number[] = [1,2,3,'1']
//操作方法添加也是不允许的
let arr:number[] = [1,2,3,]
arr.unshift('1')

var arr: number[] = [1, 2, 3]; //数字类型的数组
var arr2: string[] = ["1", "2"]; //字符串类型的数组
var arr3: any[] = [1, "2", true]; //任意类型的数组

let arr:number[][][] = [[[]],[[]],[[]]] // 三维数组

数组泛型(Array<类型>

1
2
3
let arr:Array<number> = [1,2,3,4,5]

let arr:Array<Array<number | string>> = [[1,2,3,'123'],[4,5,6]] // 二维数组

用接口表示数组

一般用来描述类数组

1
2
3
4
5
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
//表示:只要索引的类型是数字时,那么值的类型必须是数字。

arguments类数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Arr(...args:any): void {
console.log(arguments)
// 错误 arguments是类数组不能这样定义
let arr:number[] = arguments
}
Arr(111, 222, 333)

// 实际上 IArguments 是 TypeScript 中定义好了的类型,就是:
interface IArguments { // 声明数组
[index: number]: any;
length: number;
callee: Function;
}
// 我们这样使用就好了
function Arr(...args:any): void {
console.log(arguments)
//ts内置对象IArguments 定义
let arr:IArguments = arguments
}
Arr(111, 222, 333)

any 在数组中的应用

一个常见的例子数组中可以存在任意类型

1
let list: any[] = ['test', 1, [],{a:1}]

函数扩展

函数类型

1
2
3
4
5
// 注意,参数不能多传,也不能少传 必须按照约定的类型来
const fn = (name: string, age:number): string => {
return name + age
}
fn('张三',18)

函数的可选参数?

1
2
3
4
5
//通过?表示该参数为可选参数
const fn = (name: string, age?:number): string => {
return name + age
}
fn('张三')

函数参数的默认值

1
2
3
4
const fn = (name: string = "我是默认值"): string => {
return name
}
fn()

接口定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//定义参数 num 和 num2  :后面定义返回值的类型
interface Add {
(num: number, num2: number): number
}

const fn: Add = (num: number, num2: number): number => {
return num + num2
}
fn(5, 5)

interface User{
name: string;
age: number;
}
function getUserInfo(user: User): User {
return user
}

定义剩余参数

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')

函数重载

重载:方法名字相同,而参数不同,返回类型可以相同也可以不同。

如果参数类型不同,则参数类型应设置为 any。

参数数量不同你可以将不同的参数设置为可选。

1
2
3
4
5
6
7
8
9
function fn(params: number): void
function fn(params: string, params2: number): void
function fn(params: any, params2?: any): void
console.log(params)
console.log(params2)
}

fn(123
fn('123',456)

类型断言 | 联合类型 | 交叉类型

联合类型

1
2
3
4
5
6
//例如我们的手机号通常是13XXXXXXX 为数字类型 这时候产品说需要支持座机
//所以我们就可以使用联合类型支持座机字符串
let myPhone: number | string = '010-820'

//这样写是会报错的因为我们的联合类型只有数字和字符串并没有布尔值
let myPhone: number | string = true

函数使用联合类型

1
2
3
const fn = (something:number | boolean):boolean => {
return !!something // !! 强制转换类型
}

交叉类型

多种类型的集合,联合对象将具有所联合类型的所有成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface People {
age: number,
height: number
}
interface Man{
sex: string
}

const xiaoman = (man: People & Man) => {
console.log(man.age)
console.log(man.height)
console.log(man.sex)
}

xiaoman({age: 18,height:180,sex:'male'});

类型断言(疑惑点)

语法:值 as 类型 或者 <类型> 值

用途

(1)将一个联合类型推断为其中一个类型

(2)将一个父类断言为更加具体的子类

(3)将任何一个类型断言为any

(4)将any断言为一个具体的类型

1
2
3
4
5
6
7
8
9
10
11
12
interface A {
run: string
}

interface B {
build: string
}

const fn = (type: A | B): string => {
return type.run
}
//这样写是有警告的因为B的接口上面是没有定义run这个属性的
1
2
3
4
5
6
7
8
9
10
11
12
interface A {
run: string
}

interface B {
build: string
}

const fn = (type: A | B): string => {
return (type as A).run
}
//可以使用类型断言来推断他传入的是A接口的值

需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误

使用any临时断言

1
2
window.abc = 123
//这样写会报错因为window没有abc这个东西
1
2
(window as any).abc = 123
//可以使用any临时断言在 any 类型的变量上,访问任何属性都是允许的。

as const

是对字面值的断言,与const直接定义常量是有区别的

如果是普通类型跟直接const 声明是一样的

1
2
3
4
5
const names = '小满'
names = 'aa' //无法修改

let names2 = '小满' as const
names2 = 'aa' //无法修改
1
2
3
4
5
6
// 数组
let a1 = [10, 20] as const;
const a2 = [10, 20];

a1.unshift(30); // 错误,此时已经断言字面量为[10, 20],数据无法做任何修改
a2.unshift(30); // 通过,没有修改指针

类型断言是不具影响力的

在下面的例子中,将 something 断言为 boolean 。

虽然可以通过编译,但是并没有什么用 ,并不会影响结果,因为编译过程中会删除类型断言

1
2
3
4
5
6
function toBoolean(something: any): boolean {
return something as boolean;
}

toBoolean(1);
// 返回值为 1

内置对象

JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。

ECMAScript 的内置对象

Boolean、Number、string、RegExp、Date、Error

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('哔哩哔哩关注小满zs')
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 和 BOM 的内置对象

Document、HTMLElement、Event、NodeList 等

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) {

});

DOM元素的映射表

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
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

如果我们不指定返回的类型TS是推断不出来返回的是什么类型

img

指定返回的类型

img

函数定义返回promise 语法规则:Promise<T> 类型

img

当你在使用一些常用的方法的时候,TypeScript 实际上已经帮你做了很多类型判断的工作了

而他们的定义文件,则在 TypeScript 核心库的定义文件

Class类

定义类

1
2
3
4
5
6
7
8
9
//定义类
class Person {
constructor () {

}
run () {

}
}

在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明

img

如果了定义了变量不用 也会报错 通常是给个默认值 或者 进行赋值

img

img

类的修饰符

类的修饰符共有3个:public、private、protected

  1. 使用public 修饰符 可以让你定义的变量 内部访问 也可以外部访问 如果不写默认就是public

    img

  2. 使用 private 修饰符 代表定义的变量私有的只能在内部访问 不能在外部访问

    img

  3. 使用 protected 修饰符 代表定义的变量私有的只能在内部和继承的子类中访问 不能在外部访问

    img

Static 静态属性 和 静态方法

我们用static 定义的属性 不可以通过this 去访问 只能通过类名去调用

img

static 静态函数 同样也是不能通过this 去调用 也是通过类名去调用

img

注意:如果两个函数都是static 静态的是可以通过this互相调用

img

interface 定义 类

ts interface 定义类 使用关键字 implements 后面跟interface的名字多个用逗号隔开 继承还是用extends

img

抽象类

应用场景:如果你写的类实例化之后毫无用处,此时可以把他定义为抽象类

或者你也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法

例子1:下面这段代码会报错抽象类无法被实例化

1
2
3
4
5
6
abstract class A {
public name:string

}

new A()

例子2:我们在A类定义了 getName 抽象方法但没有实现

我们B类实现了A定义的抽象方法,如不实现就不报错,我们定义的抽象方法必须在派生类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class A {
name: string
constructor(name: string) {
this.name = name;
}
print(): string {
return this.name
}

abstract getName(): string
}

class B extends A {
constructor() {
super('小满')
}
getName(): string {
return this.name
}
}

let b = new B();

console.log(b.getName());

元组类型

如果需要一个固定大小的不同类型值的集合,我们需要使用元组

元组其实就是数组的变种,元组(Tuple)是固定数量的不同类型的元素的组合

元组与集合的不同之处在于,元组中的元素类型可以是不同的,而且数量固定。

元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。

1
2
3
let arr:[number,string] = [1,'string']

let arr2: readonly [number,boolean,string,undefined] = [1,true,'sring',undefined]

当赋值或访问一个已知索引的元素时,会得到正确的类型:

1
2
3
4
5
let arr:[number,string] = [1,'string']
arr[0].length //error
arr[1].length //success

//数字是没有length 的

越界元素

1
2
3
let arr:[number,string] = [1,'string']

arr.push(true)//error

对于越界的元素他的类型被限制为 联合类型(就是你在元组中定义的类型)如下图
img

应用场景 例如定义execl返回的数据

1
2
3
4
5
6
7
let excel: [string, string, number, string][] = [
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
]

枚举类型

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

使用枚举 通过enum关键字定义我们的枚举

数字枚举

例如:红绿蓝 Red = 0 Green = 1 Blue= 2 分别代表红色0 绿色为1 蓝色为2

1
2
3
4
5
enum Types{
Red,
Green,
BLue
}

这样写就可以实现应为ts定义的枚举中的每一个组员默认都是从0开始的所以也就是

1
2
3
4
5
6
enum Types{
Red = 0,
Green = 1,
BLue = 2
}
//默认就是从0开始的 可以不写值

增长枚举

1
2
3
4
5
enum Types{
Red = 1,
Green,
BLue
}

如上,我们定义了一个数字枚举, Red使用初始化为 1。 其余的成员会从 1开始自动增长。 换句话说, Type.Red的值为 1, Green为 2, Blue为 3。

字符串枚举

在一个字符串枚举里,每个成员都必须用字符串字面量,或对另外一个字符串枚举成员进行初始化。

1
2
3
4
5
enum Types{
Red = 'red',
Green = 'green',
BLue = 'blue'
}

由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 - 它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。

异构枚举

枚举可以混合字符串和数字成员

1
2
3
4
enum Types{
No = "No",
Yes = 1,
}

接口枚举

定义一个枚举Types 定义一个接口A ,他有一个属性red ,值为Types.yyds

声明对象的时候要遵循这个规则

1
2
3
4
5
6
7
8
9
10
11
enum Types {
yyds,
dddd
}
interface A {
red:Types.yyds
}

let obj:A = {
red:Types.yyds
}

const枚举

let 和 var 都是不允许的声明,只能使用const

大多数情况下,枚举是十分有效的方案。

然而在某些情况下需求很严格。

为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。

常量枚举通过在枚举上使用 const修饰符来定义

const 声明的枚举会被编译成常量

普通声明的枚举编译完后是个对象

1
2
3
4
const enum Types{
No = "No",
Yes = 1,
}

Const 声明编译之后

img

普通声明编译之后

img

反向映射

它包含了正向映射( name -> value)和反向映射( value -> name)

要注意的是 不会为字符串枚举成员生成反向映射。

1
2
3
4
5
6
7
enum Enum {
fall
}
let a = Enum.fall;
console.log(a); //0
let nameOfA = Enum[a];
console.log(nameOfA); //fall

类型推论|类型别名

类型推断

我声明了一个变量但是没有定义类型

TypeScript会在没有明确的指定类型的时候推测出一个类型,这就是类型推论

img

不能够在赋值给别的类型

img

如果你声明变量没有定义类型也没有赋值这时候TS会推断成any类型可以进行任何操作\

img

类型别名

type 关键字(可以给一个类型定义一个名字)多用于复合类型

定义类型别名

1
2
3
type str = string
let s:str = "我是小满"
console.log(s);

定义函数别名

1
2
3
type str = () => string
let s: str = () => "我是小满"
console.log(s);

定义联合类型别名

1
2
3
4
type str = string | number
let s: str = 123
let s2: str = '123'
console.log(s,s2);

定义值的别名

1
2
3
type value = boolean | 0 | '213'
let s:value = true
//变量s的值 只能是上面value定义的值

symbol类型

symbol类型的值是通过Symbol构造函数创建的。

可以传递参数做为唯一标识,只支持 string 和 number类型的参数

1
2
let sym1 = Symbol();
let sym2 = Symbol("key"); // 可选的字符串key

Symbol的值是唯一的

1
2
3
const s1 = Symbol()
const s2 = Symbol()
// s1 === s2 =>false

用作对象属性的键

1
2
3
4
5
let sym = Symbol();
let obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"

使用symbol定义的属性,是不能通过如下方式遍历拿到的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const symbol1 = Symbol('666')
const symbol2 = Symbol('777')
const obj1= {
[symbol1]: '小满',
[symbol2]: '二蛋',
age: 19,
sex: '女'
}
// 1.for in 遍历
for (const key in obj1) {
// 注意在console看key,是不是没有遍历到symbol1
console.log(key)
}
// 2.Object.keys 遍历
Object.keys(obj1)
console.log(Object.keys(obj1))
// 3.getOwnPropertyNames
console.log(Object.getOwnPropertyNames(obj1))
// 4.JSON.stringfy
console.log(JSON.stringify(obj1))

如何拿到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1.拿到具体的symbol 属性,对象中有几个就会拿到几个
Object.getOwnPropertySymbols(obj1)
console.log(Object.getOwnPropertySymbols(obj1))
// 2.es6 的 Reflect 拿到对象的所有属性
Reflect.ownKeys(obj1)
console.log(Reflect.ownKeys(obj1))
// Symbol.iterator 迭代器 和 生成器
// for of 支持遍历大部分类型迭代器 arr nodeList argumetns set map 等

var arr = [1,2,3,4];
let iterator = arr[Symbol.iterator]();

console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 3, done: false }
console.log(iterator.next()); //{ value: 4, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }

测试用例

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
interface Item {
age: number,
name: string
}

const array: Array<Item> = [{ age: 123, name: "1" }, { age: 123, name: "2" }, { age: 123, name: "3" }]

type mapTypes = string | number
const map:Map<mapTypes,mapTypes> = new Map()

map.set('1','王爷')
map.set('2','陆北')

const obj = {
aaa:123,
bbb:456
}

let set:Set<number> = new Set([1,2,3,4,5,6])
// let it:Iterator<Item> = array[Symbol.iterator]()
const gen = (erg:any): void => {
let it: Iterator<any> = erg[Symbol.iterator]()
let next:any= { done: false }
while (!next.done) {
next = it.next()
if (!next.done) {
console.log(next.value)
}
}
}
gen(array)

以下为这些symbols的列表:

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作用域排除在外。

泛型

函数泛型

下面两个函数一个是数字类型的函数,另一个是字符串类型的函数,其实就是类型不同,实现的功能是一样的,这时候我们就可以使用泛型来优化

1
2
3
4
5
6
7
8
9
10
11
function num (a:number,b:number) : Array<number> {
return [a ,b];
}

num(1,2)

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

str('独孤','求败')

泛型优化

语法函数名字后面跟一个<参数名> 参数名可以随便写

例如我这儿写了T,当我们使用这个函数的时候把参数的类型传进去就可以了 (也就是动态类型)

1
2
3
4
5
6
function Add<T>(a: T, b: T): Array<T>  {
return [a,b]
}

Add<number>(1,2)
Add<string>('1','2')

我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。

1
2
3
4
5
6
function Sub<T,U>(a:T,b:U):Array<T|U> {
const params:Array<T|U> = [a,b]
return params
}

Sub<Boolean,number>(false,1)

定义泛型接口

声明接口的时候 在名字后面加一个<参数>

使用的时候传递类型

1
2
3
4
5
6
7
8
9
10
11
interface MyInter<T> {
(arg: T): T
}

function fn<T>(arg: T): T {
return arg
}

let result: MyInter<number> = fn

result(123)

对象字面量泛型

1
2
3
4
5
6
7
let foo: { <T>(arg: T): T }

foo = function <T>(arg:T):T {
return arg
}

foo(123)

泛型约束

我们期望在一个泛型的变量上面,获取其length参数,但是,有的数据类型是没有length属性的

1
2
3
function getLegnth<T>(arg:T) {
return arg.length
}

这时候我们就可以使用泛型约束

于是,我们就得对使用的泛型进行约束,我们约束其为具有length属性的类型,这里我们会用到interface,代码如下

1
2
3
4
5
6
7
8
9
interface Len {
length:number
}

function getLegnth<T extends Len>(arg:T) {
return arg.length
}

getLegnth<string>('123')

使用keyof 约束对象

其中使用了TS泛型和泛型约束。

首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回类型是联合类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型

1
2
3
4
5
6
7
8
9
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key]
}


let o = { a: 1, b: 2, c: 3 }

prop(o, 'a')
prop(o, 'd') //此时就会报错发现找不到

泛型类

声明方法跟函数类似名称后面定义<类型>

使用的时候确定类型new Sub<number>()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Sub<T>{
attr: T[] = [];
add (a:T):T[] {
return [a]
}
}

let s = new Sub<number>()
s.attr = [1,2,3]
s.add(123)

let str = new Sub<string>()
str.attr = ['1','2','3']
str.add('123')

namespace命名空间

在工作中无法避免全局变量造成的污染,TypeScript提供了namespace 避免这个问题出现

  • 内部模块,主要用于组织代码,避免命名冲突。
  • 命名空间内的类默认私有
  • 通过 export 暴露
  • 通过 namespace 关键字定义

TypeScript与ECMAScript 2015一样,任何包含顶级import或者export的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)

命名空间中通过export将想要暴露的部分导出

如果不用export 导出是无法读取其值的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace a {
export const Time: number = 1000
export const fn = <T>(arg: T): T => {
return arg
}
fn(Time)
}


namespace b {
export const Time: number = 1000
export const fn = <T>(arg: T): T => {
return arg
}
fn(Time)
}

a.Time
b.Time

嵌套命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace a {
export namespace b {
export class Vue {
parameters: string
constructor(parameters: string) {
this.parameters = parameters
}
}
}
}

let v = a.b.Vue

new v('1')

抽离命名空间

a.ts

1
2
3
export namespace V {
export const a = 1
}

b.ts

1
2
3
4
import {V} from '../observer/index'

console.log(V);
//{a:1}

简化命名空间

1
2
3
4
5
6
7
8
9
namespace A  {
export namespace B {
export const C = 1
}
}

import X = A.B.C

console.log(X);

合并命名空间

重名的命名空间会合并

img

声明文件d.ts

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。

1
2
3
4
5
6
7
declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface 和 type 声明全局类型
/// <reference /> 三斜线指令

例如我们有一个express 和 axios

img

发现express 报错了

让我们去下载他的声明文件

npm install @types/node -D

那为什么axios 没有报错

我们可以去node_modules 下面去找axios 的package json

img

发现axios已经指定了声明文件 所以没有报错可以直接用

通过语法declare 暴露我们声明的axios 对象

declare const axios: AxiosStatic;

如果有一些第三方包确实没有声明文件我们可以自己去定义

名称.d.ts 创建一个文件去声明

例如express.d.ts

declare const express: ()=> any;

关于这些第三发的声明文件包都收录到了 npm

Mixins混入

对象混入

可以使用es6的Object.assign 合并多个对象

此时 people 会被推断成一个交差类型 Name & Age & sex;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Name {
name: string
}
interface Age {
age: number
}
interface Sex {
sex: number
}

let people1: Name = { name: "小满" }
let people2: Age = { age: 20 }
let people3: Sex = { sex: 1 }

const people = Object.assign(people1,people2,people3)

类的混入

首先声明两个mixins类 (严格模式要关闭不然编译不过)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
type: boolean = false;
changeType() {
this.type = !this.type
}
}


class B {
name: string = '张三';
getName(): string {
return this.name;
}
}

下面创建一个类,结合了这两个mixins

首先应该注意到的是,没使用extends而是使用implements。 把类当成了接口

我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 这样就能使用mixin带来的便利,虽说需要提前定义一些占位属性

1
2
3
4
5
6
class C implements A,B{
type:boolean
changeType:()=>void;
name: string;
getName:()=> string
}

最后,创建这个帮助函数,帮我们做混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码

Object.getOwnPropertyNames()可以获取对象自身的属性,除去他继承来的属性,对它所有的属性遍历,它是一个数组,遍历一下它所有的属性名

1
2
3
4
5
6
7
8
Mixins(C, [A, B])
function Mixins(curCls: any, itemCls: any[]) {
itemCls.forEach(item => {
Object.getOwnPropertyNames(item.prototype).forEach(name => {
curCls.prototype[name] = item.prototype[name]
})
})
}

Rollup & webpack构建TS项目

Rollup构建TS项目

安装依赖

  1. 全局安装rollup npm install rollup-g

  2. 安装TypeScript npm install typescript -D

  3. 安装TypeScript 转换器 npm install rollup-plugin-typescript2 -D

  4. 安装代码压缩插件 npm install rollup-plugin-terser -D

  5. 安装rollupweb服务 npm install rollup-plugin-serve -D

  6. 安装热更新 npm install rollup-plugin-livereload -D

  7. 引入外部依赖 npm install rollup-plugin-node-resolve -D

  8. 安装配置环境变量用来区分本地和生产 npm install cross-env -D

  9. 替换环境变量给浏览器使用 npm install rollup-plugin-replace -D

webpack构建TS项目

安装依赖

  1. 安装webpack npm install webpack -D

  2. webpack4以上需要 npm install webpack-cli -D

  3. 编译TS npm install ts-loader -D

  4. TS环境 npm install typescript -D

  5. 热更新服务 npm install webpack-dev-server -D

  6. HTML模板 npm install html-webpack-plugin -D

tsconfig.json配置文件

这个文件是通过tsc --init命令生成的

配置详情

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
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}

// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
"src/**/*"
],
// 指定一个排除列表(include的反向操作)
"exclude": [
"demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
"files": [
"demo.ts"
]

介绍几个常用的:

  1. include
    指定编译文件默认是编译当前目录下所有的ts文件

  2. exclude
    指定排除的文件

  3. target
    指定编译js 的版本例如es5 es6

  4. allowJS
    是否允许编译js文件

  5. removeComments
    是否在编译过程中删除文件中的注释

  6. rootDir
    编译文件的目录

  7. outDir
    输出的目录

  8. sourceMap
    代码源文件

  9. strict
    严格模式

  10. module
    默认common.js 可选es6模式 amd umd 等


笔记内容来自CSDN小满ZS老师,更多进阶内容请查看下方链接

https://so.csdn.net/so/search?q=typescript&t=blog&u=qq1195566313&s=new