TypeScript基础学习

更新时间: 2023-02-26 14:22:59

# 起航

TypeScript是JavaScript的超集,本来呢,我对它并不感兴趣,但是现在大势所趋,我还是必须得学了额。

# 类型

TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便使用。

let str = "1"; //根据初始的赋值来推导出变量的类型。以后str的类型不能改了。
//str = 2; // 报错,原因:变量在定义的时候,类型已经确定下来了,不能修改
const num = 1; // 常量不能改版指向(不能被修改)所以它的值就是它的类型
//num = "2" // 报错:原因:常量不能改变指向(不能被修改)
//const num1 = true;
1
2
3
4
5
// ts原始类型有哪些 ?
// js基础数据类型:
// number, string, booleanm, undefined, null, symbol
// ts原始类型就是js基础数据类型
let str1:string = "1";
let bool:boolean = false;
let num1:number = 10;
num1.toFixed(2);
//str1.toFixed(2) 报错
let und:undefined = undefined;
let nul:null = null;
let sy:symbol = Symbol("123");
let vo:void = undefined;

function a():void{}
// function a():undefined{} 报错
// 在ts中函数没有返回值,函数类型就是void
function b():undefined{return undefined}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 布尔值 boolean

let isDone:boolean = false
1

# 数字 number

和JavaScript一样,ts里所有的数字都是浮点数,这些浮点数的类型是number,支持十进制,八进制,二进制,十六进制,还支持InfinityNaN

let decLiteral:number = 6
let hexLiteral:number = 0xf00d
let binaryLiteral:number = 0b1010
let octalLiteral:number = 0o774
let infinityNumber:number = Infinity
let nanNumber:number = NaN
1
2
3
4
5
6

# 字符串 string

let name:string = "bob"
name = "smith"
1
2

还可以使用模板字符串,它可以定义多行文本和内嵌表达式。

let myname:string = `Gene`
let age:number = 37
let sentence:string = `Hello, my name is ${myname}.
I'll be ${age + 1} years old next month.`
// Hello, my name is Gene.        
//I'll be 38 years old next month.
1
2
3
4
5
6

# any 和 unknown

any:任意类型, unknown:不知道的类型
ts中类型有六个层级,上级的类型可以包含下级的所有类型:

  1. top type 顶级类型: any, unknown
  2. Object
  3. Number String Boolean
  4. number string boolean
  5. 普通的数字,普通字符串,true/false
  6. never
let a:unknown = 1
let b:any = '123'
let c:number = 12
let d:unknown = {'name':'daodao'}

a = b
b = a
a = c
c = a // 报错,unknown不能给number类型赋值
console.log(d.name) //报错,unknown不可以读任何属性
1
2
3
4
5
6
7
8
9
10

需要注意的是,unknown只能赋值给自身或者是any,不能给别的类型赋值,而且无法读任何属性,方法也不可以调用。

// 不推荐使用any, any绕过类型校验
let a:any = 1;
a = '10';
a = [];
a = {b:10};
a.toFixed(2); // 绕过了不检测

let n:unknown;
n = 1;
n = "10";
n = [1,2];
//n.toFixed(2); // 有做类型校验,除非上面写number,才不会报错

if(typeof n === 'number'){
    n.toFixed(2)
}else if(typeof n === 'string') {
    n.concat()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# never类型

使用never类型来表示不应该存在的状态

// 返回never的函数必须存在无法达到的终点

// 因为必定抛出异常,所以error将不会有返回值
function error(message:string):never {
    throw new Error(message)
}

// 因为存在死循环,所以Loop将不会有返回值
function loop():never {
    while(true) {

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# never与void的差异

// void类型只是没有返回值 但本身不会出错
function Void():void {
    console.log()
}

// 只会抛出异常但没有返回值
function Never():never {
    throw new Erroe('aaa')
}

// never在联合类型中会被直接移除
type A = void | number | never
1
2
3
4
5
6
7
8
9
10
11
12

# never类型的一个应用场景

type A = '唱'|'跳'|'rap'

function Ikun(value:A) {
    switch(value) {
        case '唱':
            break
        case '跳':
            break
        case 'rap':
            break
        default:
            // 是用于场景兜底逻辑
            const error:never = value
            return error            
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

但如果新来一个同事新增了一个篮球,我们必须手动找到所有switch代码并处理,否则将有可能写出bug,这个bug很难被发现,但是TS可以发现这个问题

type A = '唱'|'跳'|'rap'|'篮球'

function Ikun(value:A) {
    switch(value) {
        case '唱':
            break
        case '跳':
            break
        case 'rap':
            break
        default:
            // 是用于场景兜底逻辑
            const error:never = value
            return error            
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

由于任何类型都不能赋值给never类型的变量,所有当存在进入default分支的时候,ts的类型检查会帮我们发现这个问题。

# Object object 以及 {}

  • Object: 原型链的顶端其实就是Object,也就是意味着所有的原始类型以及对象类型最终都指向Objet,因此,在ts中Object就表示包含了所有的类型,它可以等于任何一个值。

    let a:Object = 1
    a = {'name':'abc'}
    a = 'str'
    
    1
    2
    3
  • object 常用语泛型约束,表示非原始类型,也就是除number,string,boolean,symbol,nullundefined之外的类型

    let a:object = {'name':'abc'}
    a = 1 //报错
    a = 'str' //报错
    
    1
    2
    3
  • {} 和Object一样,可以为任何类型,但是不允许修改里面没有的属性值

    let c:{} = {'a':123}
    c.a = 456 //报错
    c.b = 'dfa' //报错
    
    1
    2
    3
// object(常用) Object {}
let obj:object = {a:1};
let arr:object = [1];

//let num:object = 20 //报错,不能将类型”number"分配给类型"object"
//let str:object = "hello" //报错
// object不包含基础数据类型

let obj1:Object = {b:1};
let arr1:Object = [2,3];
let num1:Object = 2;
let str1:Object = "2";
let bool1:Object = true;
// Object 包含基础数据类型

let obj2:{} = {b:1};
let arr2:{} = [2,3];
let num2:{} = 2;
let str2:{} = "2";
let bool2:{} = true;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 数组

  • 定义普通数组类型
let arr:number[] = [1,2,3,4,5]
let arr1:boolean[] = [true,false]
let arr2:Array<number> = [1,2,3]
1
2
3
  • 定义对象数组
interface X {
    name:string
}

let arr3:X[] = [{name:'daodao'},{name:'mimi'}]
1
2
3
4
5
  • 定义二维数组
let arr4:number[][] = [[1],[2],[3]]
let arr5:Array<Array<number>> = [[1],[2],[3]]
1
2
  • 大杂烩数组
let arr6:any[] = [1,'str',true, {}]
// 也可以用元祖
let arr7:[number,string,boolean,{}] = [1,'str',true, {}]
1
2
3
  • 定义函数的剩余参数类型
function a(...args:number[]) {
    console.log(args)
    // arguments是一个类数组,定义它可以用ts内置类型 IArguments
    let a:IArguments = arguments
}

a(1,2,3) // [1,2,3]
1
2
3
4
5
6
7

# 元组类型

如果需要一个固定大小的不同类型值的集合,我们需要使用元素,元组就是数组的变种,元组是固定数量的不同类型的元素的集合。

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

元组类型还可以支持自定义名称和变为可选的

let a:[x:number,y?:boolean] = [1]
1

# 联合类型

// 这里的phone既可以是number也可以是string
let phone:number | string = 12312
phone = 'abc'

// type既可以传number也可以传boolean
let fn = function(type:number|boolean):boolean {
    return !!type
}
1
2
3
4
5
6
7
8
// | 联合类型  或
let numOrStr: number | string = 10;
numOrStr = "str"

// 1|'2' 在这里的1和'2'是类型, 常量, 表示numAndStr2 的值只能是1 或者 '2'
let numOrStr2: 1|'2' = 1
numOrStr2 = "2"
// numAndStr2 = 2 // 报错

let obj:{a:1}|{b:'3'}; // | 或,表示要么有a属性,要么有b属性,都有也可以, 不能有其他属性
obj = {a:1};
obj = {a:1,b:'3'}
obj = {b:'3'}
//obj = {a:1,c:2} // 报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 交叉类型

interface People {
    name:string;
    age:number;
}

interface Man{
    sex:number
}

// man必须同时满足People和Man
const daodao = (man:People & Man):void => {}

daodao({
    name:'daodao',
    age:18,
    sex:1
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// & 交叉类型

let a:number&string; // 不会有任何值满足这个类型,一般不会这么写

// & 都必须有 name,age, height属性,都得有
// 如果一个属性出现多次类型的设置,需要都满足
let obj:{name:string,age:number} & {height:number, age:18}
obj = {name:"zhangsan",age:18,height:1.80}

// & 换成 | 才可以少属性
1
2
3
4
5
6
7
8
9
10

# 联合交叉类型

// | &

// &&优先于||
// console.log(1||2&&3); //1

// & 优先于 |
let obj:{name:string} & {age:number} | {name:number} & {age:string}

obj = {
    name:123,
    age:"123"
}
obj = {
    name:"123",
    age:123
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 类型断言

类型断言不可以乱用哦!

let fn1 = function(num:number|string):void {
    // 类型断言只能欺骗ts编译器,不能避免运行时的错误
    console.log((num as string).length)
}

fn1(12345)

interface A {
    run:string;
}

interface B {
    build:string;
}

let fn2 = (type: A|B):void => {
    // 类型断言只能欺骗ts编译器,不能避免运行时的错误
    // !类型断言不能乱用!!
    console.log((type as A).run)
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 类型别名

// 自定义一个类型
type StrOrNum = string | number

let str:StrOrNum = "1";
str = 1;

type ObjType = {a:number&2, b:string}
// type ObjType = {c:string}
let obj:ObjType = {
    a:2,
    b:"123"
}

// interface 和 type 的区别
// 都可以用来自定义类型
// 类型别名支持联合和交叉定义
// 类型别名不支持重复定义,接口可以

interface AItf{
    a:string
}
// 用类型别名保存接口上的某个类型
type Atype = AItf['a']
let str2:Atype = "10"

type color = 'red' | 'blue' | 'green' | string & {}
let c:color = 'red'
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 Example {
    name: string;
    age:number;
}

let a:Example = {
    name:'daodao',
    age:18
}
1
2
3
4
5
6
7
8
9

# 接口重名

重名的两个接口会合并成为一个接口

interface A {
    name:string;
    age:number;
}
interface A {
    money:number
}
let daodao:A = {
    name:'daodao',
    age:18,
    money:1000000000000
}
1
2
3
4
5
6
7
8
9
10
11
12

# 任意key

有时候实在不能把每个属性都列举出来,比如后端返回了其他的属性

interface B {
    name:string;
    age:number;
    [propName:string]:any;
}
let b:B = {
    name:'b',
    age:100,
    a:1,
    b:2,
    c:3
}
1
2
3
4
5
6
7
8
9
10
11
12

# 只读和可缺省属性

有的属性比如id,并不希望它能被外部修改,可以加上readonly变为只读属性,而有的属性可有可无,可以加?代表可缺省

interface C{
    readonly id:number;
    name:string;
    age?:number;
    readonly cb:() => boolean;
}
let c:C = {
    id:1,
    name:'c',
    cb:() => {
        return false
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 接口继承

接口继承和接口重名类似

interface D {
    name:string
}
interface E extends D {
    age:number
}

let e:E = {
    name:'e',
    age:20
}

interface NameItf{
    readonly name:string // readonly 属性名,表示这个属性只允许读取,修改就报错
}

interface AgeItf{
    age?:number //属性名?表示这个属性可以缺省。(定义数据的时候不写也没问题)
}

// 接口继承的格式,特点是具有父接口的属性类型
interface PersonItf extends NameItf,AgeItf {
    height:number
}

let p:PersonItf;
p = {
    name:"张三",
    height:1.80
}
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

# 定义数组

interface ArrItf{
    //[idx:number]下标类型:值类型
    [idx:number]:number|string
}
1
2
3
4

# 定义函数

定义函数类型就想一个只有参数列表和返回值类型的函数定义

interface Fn {
    (name:string):number[]
}

const fn:Fn = function(name:string) {
    return [1]
}

interface FnItf{
    // 形参及类型:返回值类型
    (p:string,a:number):void
}
let fn:FnItf = (p:string,a:number) => {

}

fn("",1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 默认参数 参数名:number=3 这个参数的默认值是3
function fn(a:number,b:number=3):number {
    return a+b
}

fn(1,2)

fn(5)

// 缺省参数 参数名?表示可以被缺省的参数
function fn1(a:number,b?:number) {
    return 1
}

fn1(1,2)
fn1(1)

// 剩余参数
function fn2(a:number,b:number,...arr:number[]){
    console.log(a,b)
    console.log(arr)
}
fn2(1,2,3,4,5)

let arr1 = [1,2,3]
let arr2 = [...arr1]
arr1[0] = 4
console.log(arr1) // [4,2,3]
console.log(arr2) // [1,2,3]

let obj1 = {a:1,b:2,c:[1,2,3]}
let obj2 = {...obj1} //浅拷贝
obj1.a = 100
obj1.c[0] = 1000
console.log(obj1) // {a:100,b:2,c:[1000,2,3]}
console.log(obj2) // {a:1, b:2, c:[1000,2,3]}
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

# 内置对象

# ECMAScript的内置对象

const regexp:RegExp = /\w\d\s/ 

const date:Date = new Date()

const error:Error = new Error('错误')

const b:Boolean = new Boolean(1)

const n:Number = new Number(true)

const s:String = new String('123')
1
2
3
4
5
6
7
8
9
10
11

# DOM和BOM的内置对象

其实这个不用记,ts会自动推断,鼠标移上去就可以看了

const list:NodeListOf<HTMLLIElement> = document.querySelectorAll('li')

const body:HTMLElement = document.body

const div:HTMLDivElement | null = document.querySelector('div')

document.body.addEventListener('click',(e: MouseEvent) => {

})
1
2
3
4
5
6
7
8
9

# Promise

function promise():Promise<number> {
    return new Promise<number>((resolve,reject) => {
        resolve(123)
    })
}

interface DataItf{
    a:number,
    b:number
}

interface ResItf {
    code:number;
    data:DataItf[]  //{a:number,b:number}[],
    message:string
}

// promise对象: p对象名:Promise<res的类型>
let p:Promise<ResItf> = new Promise((resolve, reject) => {
    resolve({
        code:0,
        data:[{a:1,b:2},{a:11,b:22}],
        message:""
    })
})

p.then(res => {
    if(res.code === 0) {
        res.data.map(item => item.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
25
26
27
28
29
30
31

# Class

// 定义类的同时,会创建一个相同名字的接口
class Person {
    // 定义属性前,应该先声明这个属性的类型,也可以同时设置默认值
    myName:string = "默认名称"

    constructor(n:string) {
        this.myName = n
    }

    getName() {
        return this.myName
    }
}

let p = new Person("zhangsan")
console.log(p.myName) // zhangsan
console.log(p.getName()) //zhangsan

// 以上这个类,相当于下面这个接口
// interface Person {
//     myName:string,
//     getName:() => string
// }

let obj:Person = {
    myName:"",
    getName() {
        return ""   
    }
}
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

# 类的继承

class Person {
    myName:string

    constructor(n:string) {
        this.myName = n
    }

    getName() {
        return this.myName
    }
}

class Male extends Person {

    age:number

    constructor(n:string,age:number) {
        super(n) // 调用回父类的constructor,并把参数传进去
        this.age = age
    }

    getName() { // 重写
        return "我叫"+this.myName
    }

    getAge() {
        return this.age
    }
}

let m = new Male("zhangsan",17)
console.log(m.myName) //zhangsan
console.log(m.getName()) //我叫zhangsan
console.log(m.getAge()) // 17
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

# 类的修饰符

// 类里面,定义的属性,默认的修饰符就是public,public修饰的属性和方法都可以在类的内部,类的外部可以访问,子类也可以访问。
// protected 受保护的,类里面,子类里面都可以访问,类的外面不能访问
// private 私有的,在本类里面可以访问,子类和类的外面不能访问
// readonly 设置属性只读,不能被修改
class Person{
    public readonly myName:string
    // protected myName:string
    // private myName:string
    static title:string = "title的值" // 静态属性/成员,是给类去用的
    constructor(n:string) {
        this.myName = n
    }

    public getName() {
        return this.myName
    }
}

console.log(Person.title)
Person.title = "修改后的title的值"
console.log(Person.title) // 修改后的title的值


let p = new Person("李四")
//console.log(p.title) 报错!

class Male extends Person{
    age:number
    constructor(name:string, age:number) {
        super(name)
        this.age = age
    }
    getName() {
        return "我叫"+this.myName
    }
}

let m = new Male("张三",17)
console.log(m.myName)
console.log(m.age)
console.log(m.getName())
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

# 抽象类

用abstract定义的类是抽象类,抽象类不可以被实例化

abstract class A {
    name:string
    constructor(name:string) {
        this.name = name
    }

    print(): string {
      return this.name
    }

    abstract getName():string
}

// B类实现了A定义的抽象方法,如果不实现就会报错,我们定义的抽象方法必须在派生类实现
class B extends A {
   constructor() {
      super('小满')
   }
   getName(): string {
      return this.name
   }
}
 
let b = new B();
 
console.log(b.getName());
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

# 工具类型 Reqiured 和 Partial

interface PItf {
    name:string;
    age:number;
    height?:number;
}

// interface PItf2 {
//     name?:string;
//     age?:number;
// }

//type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
/*
keyof T   name|age
{
    name?: string|undefined;
    age?: number|undefined;
}
for(key in 对象)
// Partial 部分的
*/
// 作用:把<>里面这个接口类型的属性设置为可缺省的属性
let obj:Partial<PItf> = {
    name:"123"
}

type Required<T> = { [P in keyof T]-?: T[P]; }
/**
 * keyof T name|age|height
 * -? 抵消,去掉这个问号的效果
 * {
 *      name:string,
 *      age:number,
 *      height:number
 * }
 */
// 作用:把<>里的这个接口类型的属性设置为不可缺省的属性
let obj2:Required<PItf> = {
    name:"",
    age:12,
    height:1.80
}
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

# 枚举

// 枚举不是用来定义类型,列举数据用的
// enum Xxx{
//     a = 10,
//     b = "200"
// }

enum StatusCode{
    success=200,
    paramsError=400,
    serverError=500
}

let code:string|number= 200;
if(code===StatusCode.success){
    console.log("成功")
}else if(code===StatusCode.paramsError){
    console.log("失败")
}else if(code===StatusCode.serverError) {
    console.log("失败,服务器问题")
}

enum StatusCode2{
    success,
    paramsError=400,
    serverError
}
console.log(StatusCode2.success, StatusCode.paramsError, StatusCode.serverError)
// 0, 400, 401
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