TypeScript基础

TypeScript基础

写在前面

本文参考作者小满zstypeScript专栏

视频链接:【小满TypeScript基础教程全集(完结)】

TS以及TS与JS的区别
首先,TypeScript和JavaScript都是脚本语言。

JavaScript是轻量级的解释性脚本语言,可嵌入到HTML页面中,在浏览器端执行。

TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,TypeScript包含了JavaScript的库和函数,TypeScript上可以写任何的JavaScript,调用任何的JavaScript库。而且本质上TypeScript扩展了JavaScript的语法解决JavaScript的“痛点”:弱类型和没有命名空间,导致很难模块化。
区别

  1. 语法层面:TypeScript = JavaScript + Type(TS 是 JS 的超集),对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。而且完全兼容JS,换言之,任何的TS代码都可以直接当成JS使用。

  2. 执行环境层面:浏览器、Node.js 可以直接执行 JS,但不能执行 TS(Deno 可以执行 TS)

  3. 编译层面:TS 有编译阶段,要通过编译器编译为JS,然后再交由JS解析器执行。JS 没有编译阶段(只有转译阶段和 lint 阶段)

  4. 编写层面:TS拥有了静态类型,更加严格的语法,更强大的功能,更难写一点,但是类型更安全

  5. 文档层面:TS 的代码写出来就是文档,IDE 可以完美提示。JS 的提示主要靠TS

TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;

TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;

同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。

常用指令:

npm init -y创建package.json配置文件

tsc -init创建tsconfig.json配置文件

tsc -w监视文件实时变化,并自动根据ts文件创建js文件。

tsc-node 文件名执行ts文件

常用关键字

  1. let: 用于声明变量。
  2. const: 用于声明常量。
  3. class: 用于定义类。
  4. interface: 用于定义接口。
  5. type: 用于定义类型别名。
  6. extends: 用于类和接口之间的继承。
  7. implements: 用于类实现接口。
  8. public/private/protected: 用于定义属性和方法的访问权限。
  9. static: 用于定义静态属性和方法。
  10. export/import: 用于导出和导入模块。
  11. super: 用于调用父类的构造函数和方法。
  12. this: 用于表示当前对象。
  13. void: 用于表示函数没有返回值。
  14. any: 用于定义任意类型的变量。
  15. never: 用于表示永远不会发生的情况。
  16. null/undefined: 用于表示空值。
  17. keyof: 用于获取对象的所有键名。
  18. in: 用于判断一个属性是否存在于一个对象中。
  19. as: 用于类型断言。
  20. readonly: 用于定义只读属性。

一、基础类型

基础类型:booleannumberstringnullundefined以及ES6的Symbol和ES10的BigInt

1.字符串类型

使用string定义。

// 普通声明
let a: string = '1234'
// 也可以使用es6的字符串模板
let str: string = `llll${a}`
console.log(str)  // llll1234

2.数字类型

支持NaNInfinity、十六进制、十进制等。

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

3.布尔类型

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

// 这样会报错,因为事实上new Boolean() 返回的是一个 Boolean 的对象
let createBoolean: boolean = new Boolean(1)  // 前后类型不对应
// 需要改成
let createBoolean: Boolean = new Boolean(1)  // 前后类型对应
console.log(createBoolean);  // [Boolean: true]
// 也可以这样写
let boolean1: boolean = true // 直接使用布尔值
let boolean2: boolean = Boolean(1) //也可以通过函数返回布尔值
console.log(boolean1);  // true
console.log(boolean2);  // true

4.空值类型void

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

function voidFn(): void {
    console.log('test void')
}
voidFn()  // test void

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

void也可以定义undefined和null类型

let u: void = undefined
let n: void = null

5.null和undefined类型

let u: undefined = undefined  // 定义undefined
let n: null = null  // 定义null

void和null以及undefined的区别

undefinednull是所有类型的子类型,也就是说,undefinednull类型的变量可以赋值给stringnumber等基本类型,但是void是不可以的,看以下代码:

let void1: void = undefined
let string1: string = "111"
void1 = string1  // 报错,不能将类型void分配给string

以下这样是没问题

let null1: null = null
let string1: string = "111"
null1 = string1  // 没有报错

Tips:

如果你在tsconfig.json配置文件中开启了严格模式,就不能将类型null分配给类型void。可以修改配置文件关闭严格模式

{
    "compilerOptions":{
        "strict": false
    }
}

二、任意类型(顶级类型)

在TypeScript3.0引入unknown类型之后,总共有两种顶级类型,分别是:anyunknown

  • 顶级类型没有强制限定哪种类型,随时切换类型都可以 我们可以对any进行任何操作,不需要检查类型
  • 声明变量的时候没有指定任意类型默认为any
  • 缺点:如果使用any就失去了TS类型检测的作用

unknown和any的区别

  1. unknownany更加严格,unknown不能赋值给其他类型,它可赋值的对象只有unknownany,而any可以赋值给任意类型。

    //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= bbb		   // 不报错
    let ccc:unknown = bbb      // 不报错
    
  2. unknown不能调用对象的属性和方法,而any类型可以,并且any类型在对象没有这个属性的时候获取也不会报错。

    let obj1: any = {a:1}
    console.log(obj1.a);  // 1
    console.log(obj1.b);  // undefined,any类型的对象中没有b属性,调用时也不会报错 
    
    let obj2: unknown = {a:1}
    obj2.a  // 报错,即使unknown类型的对象内包含a属性也无法调用
    

三、接口和对象类型

在TypeScript中,我们定义对象的方式要用关键字interface(接口),可以理解为使用interface来定义一种约束,让数据的结构满足约束的格式。定义方式如下:

interface Person {
    name: string,
    age: number
}

const person1: Person = {
    name: '小5'
}
// 会报错,因为定义的接口约束有两个参数,实例化的对象person1缺少age属性。

接口重名会怎么样?

  • 重名接口内的约束会被合并
  • 重名接口内如果有重名的约束,那么它们的类型必须保持一致,否则会报错
interface Person {
    name: string,
    age: number
}
// 重名接口
interface Person {
    name: string,
    school: string
}

const person1: Person = {
    name: '小5',
    age: 18,
    school: 'ncu' // 合并约束
}

使用操作符?设置可选属性

interface Person {
    name: string,
    age?: number  // 设置可选属性age
}

const person1: Person = {
    name: '小5'  // 即使没有添加age也不会报错
}

使用readonly设置只读属性

interface Person {
    name: string,
    readonly age: number  // 设置只读属性age
}

const person1: Person = {
    name: '小5',
    age: 18
}

person1.name = '小6'  // 不报错,可以正常修改对象属性
person1.age = 81  // 报错,因为age是只读属性,不可以被修改

使用[propName:string]设置任意属性

设置了任意属性后,允许在实例化对象中添加新的任意属性。

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

interface Father {
    name: string,
    [propName: string]: any
}

const father1: Father = {
    name: '小5',
    age: 18,
    school: 'ncu'
}

接口继承

和ES6的类的继承类似。

interface Father {  // 定义父接口
    name: string,
}

interface Son extends Father {  // 定义子接口并继承父接口
    age: number
}
// 接口继承后的结果和重名接口合并类似
const son1: Son = {
    name: '小5',
    age: 18  // 必须满足约束,否则报错
}

使用interface定义函数类型

// 按 函数参数:函数返回值 的约束格式 定义了Fn
interface Fn {
    (name: string):number[]  // 函数参数:函数返回值
}
// 实例化Fn函数
const fn: Fn = function (name: string) { // string类型的、名为name的形参
    return [1]  // 返回值为:number类型的数组
}

四、数组类型

常规定义方法: 名字:类型[]

let arr: number[] = [123]  // 定义一个数字类型的数组
console.log(arr);  // [123]
arr.unshift(1)
console.log(arr);  // [1, 123]

泛型定义方法:名字:Array<类型>

let arr:Array<number> = [1,2,3]
console.log(arr);  // [ 1, 2, 3 ]
arr.unshift(5)
console.log(arr);  // [ 5, 1, 2, 3 ]

用接口表示数组

对象数组

interface Arr {
    name: string
}

let arr: Arr[] = [{ name: '小5' }, { name: '小满' }]
console.log(arr);  // [{ name: '小5' }, { name: '小满' }]

描述类数组的一般写法

interface NumberArr {
    [index: number]: number
    // 表示:只要索引的类型是数字时,那么值的类型必须是数字
}

let fibonaci: NumberArr = [1, 1, 2, 3, 5]
console.log(fibonaci);  // [ 1, 1, 2, 3, 5 ]

多维数组

// 常规写法
let data:number[][] = [[1, 2], [3, 4]]
// 泛型写法
let data:Array<Array<number>> = [[1, 2], [3, 4]]

arguments类数组

function Arr(...args: any): void {
    console.log(arguments);  
    //错误的arguments 是伪数组不能这样定义
    let arr:number[] arguments
}

Arr(11,22,33)  // [Arguments] { '0': 11, '1': 22, '2': 33 }
function Arr(...args: any): void {
    console.log(arguments);  // [Arguments] { '0': 11, '1': 22, '2': 33 }
    // ts内置对象IArguments定义
    let arr: IArguments = arguments
    console.log(arr);        // [Arguments] { '0': 11, '1': 22, '2': 33 }
    
}

Arr(11,22,33)
//其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

五、函数扩展

常规函数

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

函数的可选参数

//通过?表示该参数为可选参数
const fn = (name: string, age?:number): string => {
    return name + age
}
console.log(fn('张三'));  // 张三undefined

函数参数的默认值

const fn = (name: string = "我是默认值"): string => {
    return name
}
console.log(fn());  // 我是默认值

接口定义函数

接口作为函数的类型

// 定义参数 num 和 num2  :后面定义返回值的类型
interface Add {
    (num: number, num2: number): number
}
// Add作为函数fn的类型
const fn: Add = (num: number, num2: number): number => {
    return num + num2
}
console.log(fn(5, 5));  // 10

接口作为形参类型

interface User {
    name: string;
    age: number;
}
// User接口作为形参user的参数类型
function getUserInfo(user: User): User {
  return user
}

console.log(getUserInfo({name: '小5', age: 18}));  // { name: '小5', age: 18 }

在接口中定义this的类型

interface Obj {
    user: number[]
    add: (this:Obj, num: number) => void
}

// ts 可以定义this 的类型 在js中无法使用 必须是第一个参数定义this 的类型
let obj: Obj = {
    user: [1, 2, 3],
    add(this: Obj, num: number) {
        this.user.push(num)
    }
}

console.log(obj);  // { user: [ 1, 2, 3 ], add: [Function: add] }
obj.add(5)  // 传参的时候忽略this,直接传参
console.log(obj);  // { user: [ 1, 2, 3, 5 ], add: [Function: add] }

定义剩余参数

const fn = (array: number[], ...items: any[]): any[] => {
    console.log(array,items);
    return items
}

const item =  fn([1,2,3],"1", true);  // [ 1, 2, 3 ] [ '1', true ]
console.log(item);  // [ '1', true ]

函数重载

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

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

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

// 函数重载
let user: number[] = [1, 2, 3]

function findNum(add: number[]): number[]  // 如果传的是一个number类型的数组 那就做添加
function findNum(id: number): number[]  // 如果传入了id就是单个查询
function findNum(): number[]  // 如果没有传入东西就是查询全部

function findNum(ids?: number | number[]): number[] {
    if (typeof ids == 'number') {  // 如果传入的参数是number类型
        return user.filter(v => v === ids)
    }
    else if (Array.isArray(ids)) {  // 如果传入的参数是数组类型
        user.push(...ids)
        return user
    } else {
        return user
    }
}

console.log(findNum());  // [1, 2, 3]
console.log(findNum(2)); // [2]
console.log(findNum([5, 6]));  // [1, 2, 3, 5, 6]

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

联合类型

例如,我们的手机号通常是13xxxxxxxxxxx 为数字类型,这时候产品说需要支持座机,所以我们就可以使用联合类型来支持座机字符串。

let myPhone: number | string = '010-820'

函数使用联合类型

let fn = function (type: number | boolean): boolean {
    return !!type
}
// !! 表示将数据类型强制转换成boolean类型,类似Boolean(),如!!1得到true,!!0得到false

let result1 = fn(false)
let result2 = fn(5)
console.log(result1);  // false
console.log(result2);  // true

交叉类型

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

使用&联合多个接口,类似于继承和接口重名属性合并。

interface People{
    name: string,
    age: number
}

interface Man {
    sex: number
}

const xiao5 = (man: People & Man): void => {
    console.log(man);
}

xiao5({
    name: '小5',
    age: 18,
    sex: 1
})  // { name: '小5', age: 18, sex: 1 }

类型断言

语法:

  1. 值 as 类型 例如:value as string
  2. <类型>值 例如: <string>value
interface A {
       run: string
}
 
interface B {
       build: string
}
 
const fn = (type: A | B): string => {
       return type.run
}
//这样写是有警告的应为B的接口上面是没有定义run这个属性的

使用断言欺骗ts编译器

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

使用any临时断言

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

as const临时断言

as const是对字面值的断言,与const直接定义常量有共同点也有不同点。

如果是字面值是普通类型,那么as const跟直接const声明式一样的,都无法被修改。

const myName = '小5'
myName = '小满'  // 报错,无法修改
let myName = '小5' as const
myName = '小满'  // 报错,无法修改

如果字面值是引用类型,那么as const临时断言后,字面值将无法被修改,但是const定义的引用类型是可以修改的,因为修改引用类型的值并没有修改它的指针。

const a1 = [10, 20]
a1.unshift(30)
console.log(a1);  // [ 30, 10, 20 ]
let a1 = [10, 20] as const 
a1.unshift(30)  // 报错,无法修改

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

如下面的例子,将something断言为boolean虽然可以通过编译,但是并没有结果造成改变,因为编译过程中会删除类型断言。

function toBoolean(something: any): boolean {
    return something as boolean;
}
 
console.log(toBoolean(1));  // 1

七、内置对象

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

ECMAScript 的内置对象

Boolean、Number、stringRegExpDateError

let b: Boolean = new Boolean(1)
console.log(b)  // [Boolean: true]
let n: Number = new Number(true)
console.log(n)  // [Number: 1]
let s: String = new String('哔哩哔哩关注小满zs')
console.log(s)  // [String: '哔哩哔哩关注小满zs']
let d: Date = new Date()
console.log(d)  // 2023-05-10T14:04:37.229Z
let r: RegExp = /^1/
console.log(r)  // /^1/
let e: Error = new Error("error!")
console.log(e)  // Error: error!...

DOM和BOM的内置对象

DocumentHTMLElementEventNodeList

常见的HTML元素类型:

  1. HTML(元素名称)Element,如HTMLDivElement
  2. HTMLElement
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
// 读取div 这种需要类型断言 或者加个判断应为读不到返回null
let div:HTMLElement = document.querySelector('div') as HTMLDivElement

// HTML(元素名称)Element HTMLElement Element
let div:NodeListOf<HTMLDivElement | HTMLElement> = document.querySelectorAll('div footer')

document.addEventListener('click', function (e: MouseEvent) {
    
});


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

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

指定返回类型

let promise: Promise<string> = new Promise((r) => r('小5'))  // 返回字符串类型
promise.then(res => {
    return res.length
})

八、Class类

使用TS定义一个简单的类

// 定义类
class Person {
    name: string
    age: number
    constructor (name: string, age: number) {
        this.name = name
    }
    run () {
        
    }
}
// 实例化类
new Person('小5', 18)

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

类的修饰符

  1. private: 只能在类的内部使用
  2. protected:只能给子类和内部去使用
  3. public:哪儿都能用,不写默认就是public

类的继承

使用extends关键字实现类的继承,子类继承父类,子类必须在constructor构造函数中的首行调用super()初始化父类的属性。

super()的原理:父类的prototype.constructor.call(),也就是说子类也可以给父类传参。

另外,可以使用super()调用父类的方法。

如下:

class Father {
    constructor(name: string) {  // 接收子类传来的参数
        
    }
    play(){}
}
class Son extends Father {
    constructor() {
        super('小5')  // 给父类传参
        super.play()  // 使用super调用父类方法
    }
}

static静态属性/方法

  • 静态属性/方法不能通过this去访问,只能通过类名去调用
  • 如果两个函数都是static静态的,那它们可以通过this相互调用
class Obj {
    name: string
    static age: number  // 定义静态属性
    constructor(name: string, age: number) {
        this.name = name
        this.age = age  // 报错
        this.play()  // 报错
    }
    static play() {
        
    }
    static come() {
        this.play()  // 不报错
    }
}
}
Obj.age  // 不报错
Obj.play()  // 不报错
const obj = new Obj('小5')
obj.name
obj.age // 报错
obj.play()  // 报错

interface定义类

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

interface Options {
    el: string | HTMLElement
}

// 定义一个约束Vue类的接口
interface VueCls {
    options: Options
    init(): void
}

// 再定义一个约束Vue类的接口
interface VueCls2 {
    test: number
}

class Vue implements VueCls, VueCls2 {
    options: Options
    test: number
    constructor(options: Options, test: number) {
        this.options = options
        this.test = test
    }
    init(): void {
        
    }
}
// 实例化类
new Vue({ el: '#app'}, 1)

get和set方法

class Ref {
    _value: string
    constructor(value: string) {
        this._value = value
    }

    get value() {  // 名字不能和属性名相同
        return this._value + 'vvv'
    }

    set value(newValue) {  // 名字不能和属性名相同
        this._value = newValue + '小5'
    }
}

const ref = new Ref('哈哈哈')
console.log(ref.value);  // 哈哈哈vvv
ref.value = '坏人'
console.log(ref.value);  // 坏人小5vvv

抽象类

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

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

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

abstract class A {
   public name:string
   
}
new A()

我们在A类定义了 getName 抽象方法但为实现

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

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)是数组的变种,它是固定数量的不同类型的元素的组合。

元组与集合不同之处在于,元组中的元素类型可以是不同的,而且数量固定。元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。

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

let arr:[number, string] = [1, 'string']
console.log(arr);  // [ 1, 'string' ]
let arr2: readonly [number, boolean, string, undefined] = [1, true, 'string', undefined]
console.log(arr2);  // [ 1, true, 'string', undefined ]
let arr3:[number,string] = [1,'string', 2]  // 报错, 目标仅允许存入2个元素

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

let arr:[number,string] = [1,'string']
console.log(arr[0].length);  // 报错,类型number上不存在属性length
console.log(arr[1].length);  // success

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

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

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

let excel: [string, string, number, string][] = [
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
    ['title', 'name', 1, '123'],
]
console.log(excel);

十、枚举类型

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

数字枚举

enum Color {
    red,
    green,
    blue
}
console.log(Color.red);  // 0
console.log(Color.green);  // 1
console.log(Color.blue);  // 2

增长数字枚举

enum Color {
    red = 1,
    green,
    blue
}

console.log(Color.red);  // 1
console.log(Color.green);  // 2
console.log(Color.blue);  // 3

字符串枚举

enum Color {
    red = 'red',
    green = 'green',
    blue = 'blue'
}

console.log(Color.red);  // red
console.log(Color.green);  // green
console.log(Color.blue);  // blue

字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。

异构枚举

enum Color {
    red = 1,
    green = 'green',
}

console.log(Color.red);  // 1
console.log(Color.green);  // green

接口枚举

enum Color {
    red = 1,
    green = 'green',
}

interface A {
    red: Color.red
}

let obj: A = {
    red: Color.red  // 必须和枚举上的属性值保持一致
    // red: 1  // 这样也不会错
}

const声明编译

反向映射

枚举类型包含了正向映射(name -> value)和反向映射(value -> name)

enum Enum {
   fall
}
let a = Enum.fall;  // 正向映射
console.log(a); // 0
let nameOfA = Enum[a];  // 反向映射
console.log(nameOfA); // fall

注意:字符串枚举成员没有反向映射。

十一、类型推论|类型别名关键字type

什么是类型推论?

假如我声明了一个变量但是没有定义类型,TypeScript会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。

在这里插入图片描述

如果声明时没赋值,也没定义类型,那么TS会默认推断为any类型。

type关键字

在TypeScript中,type关键字用来定义类型别名。通过定义类型别名,可以更方便地使用复杂的类型和组合类型。以下是一些使用type关键字的示例:

  1. 基本类型别名:
type MyString = string;
type MyNumber = number;

let s:MyString = '我是小5'
console.log(s)  // 我是小5
  1. 用type关键字定义复杂类型别名:
type Point = {
   x: number;
   y: number;
};

type Person = {
   name: string;
   age: number;
   address: string;
};

type Circle = {
   center: Point;
   radius: number;
};
  1. 用type关键字定义联合类型:
type ID = string | number;
type User = {
   id: ID;
   name: string;
};

let s: ID = 123
console.log(s);  // 123
let s2: ID = '1234'
console.log(s2);  // 1234

let user: User = {
    id: 1,
    name: '小5'
}
console.log(user);  // { id: 1, name: '小5' }
  1. 用type关键字定义交叉类型:
type WithTitle = {
   title: string;
};

type WithDescription = {
   description: string;
};

type MyType = WithTitle & WithDescription;
  1. 用type关键字定义泛型类型别名:
type MyArray<T> = T[];
type MyMap<T> = Record<string, T>;

总之,通过使用type关键字定义类型别名,我们可以更简洁、更清晰地表示复杂的类型和组合类型,以及重复使用已有的类型并进行扩展。

type高级用法

左边的值会作为右边值的子类型遵循图中上下的包含关系

type a = 1 extends number ? 1 : 0 //1
 
type a = 1 extends Number ? 1 : 0 //1
 
type a = 1 extends Object ? 1 : 0 //1
 
type a = 1 extends any ? 1 : 0 //1
 
type a = 1 extends unknow ? 1 : 0 //1
 
type a = 1 extends never ? 1 : 0 //0

ts类型级别

在这里插入图片描述

十二、never类型

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

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

// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
    throw new Error(message);
}
 
// 因为存在死循环,所以 loop 将不会有返回值
function loop(): never {
    while (true) {
    }
}

never和void的差异

  1. void类型只是没有返回值,但本身不会出错,而never只会抛出异常而没有返回值。

    //void类型只是没有返回值 但本身不会出错
    function Void():void {
        console.log();
    }
    
    //只会抛出异常没有返回值
    function Never():never {
        throw new Error('aaa')
    }
    
  2. 如下,当我们将光标移至type A上时会发现,只有voidnumbernever在联合类型中会被直接移除。

    type A = void | number | never
    

never类型的一个常见应用场景

用于场景兜底逻辑

假如一个新来的同事在type A中新增了一个"篮球",我们必须手动找到所有switch代码并处理,否则将有可能引入BUG。而这将是一个“隐蔽型”的BUG,如果回归面不够广,很难发现此类BUG,而兜底逻辑会自动提示报错,根据兜底逻辑的提示能够快速解决此BUG。

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

function ikun(value: A) {
    switch (value) {
        case "唱":
            break
        case "跳":
            break
        case "rap":
            break
        case "篮球":  // 如果没加这个case,default下面的兜底逻辑就会触发,提示报错
            break
        default:
            // 用于场景兜底逻辑
            const error: never = value
            break
    }
}

十三、Symbol类型

自ECMAScript2015起,Symbol成为了一种新的原生类型,就像number和string一样。

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

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

let sym1 = Symbol()
let sym2 = Symbol('key')  // 可选的字符串key

Symbol的值是唯一的

const s1 = Symbol()
const s2 = Symbol()
console.log(s1 === s2)  // false

Symbol经常用作对象属性的键

let sym = Symbol()
let obj = {
    [sym]: "value"
}
console.log(obj[sym])  // value

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

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)  // age sex
}
// 2 Object.keys 遍历
console.log(Object.keys(obj1))  // [ 'age', 'sex' ]
// 3 getOwnPropertyNames
console.log(Object.getOwnPropertyNames(obj1))  // [ 'age', 'sex' ]
// 4 JSON.stringfy
console.log(JSON.stringify(obj1))  // {"age":19,"sex":"女"}

如何拿到Symbol相关属性

// 1 拿到具体的symbol 属性,对象中有几个就会拿到几个
console.log(Object.getOwnPropertySymbols(obj1))  // [ Symbol(666), Symbol(777) ]
// 2 es6 的 Reflect 拿到对象的所有属性
console.log(Reflect.ownKeys(obj1))  // [ 'age', 'sex', Symbol(666), Symbol(777) ]

Symbol.iterator迭代器和生成器for of

支持遍历arrnodeListargumentssetmap等类型。

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 }

定义一个可以遍历多种类型的函数

interface Item {
    age: number,
    name: string
}

// 对象数组
const array: Array<Item> = [{ age: 123, name: "1" }, { age: 123, name: "2" }, { age: 123, name: "3" }]
// Map
type mapTypes = string | number
const map:Map<mapTypes,mapTypes> = new Map()
map.set('1','王爷')
map.set('2','陆北')
// 集合Set
let set: Set<number> = new Set([1, 2, 3, 4, 5, 6])
// 自定义一个可以遍历多种类型的函数
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)
        }
    }
}
// 普通对象
const obj = {
    aaa:123,
    bbb:456
}
// 遍历对象数组
gen(array)
// 遍历Map
gen(map)
// 遍历集合
gen(set)
// 遍历普通对象
gen(obj)  // 报错,因为对象没有iterator

我们平时开发中是不会手动调用iterator的,因为它有语法糖,也就是for(let 单项 of 迭代对象)

但是需要注意的是:for(let 单项 of 迭代对象)是不能循环对象的,因为对象没有iterator

用法如下:

for (let value of map) {
    console.log(value)
}
// [ '1', '王爷' ]
// [ '2', '陆北' ]

数组解构的原理其实也是调用迭代器的。

以下代码实现了一个让对象支持for of 的迭代器

 
const obj = {
    max: 5,
    current: 0,
    [Symbol.iterator]() {
        return {
            max: this.max,
            current: this.current,
            next() {
                if (this.current == this.max) {
                    return {
                        value: undefined,
                        done: true
                    }
                } else {
                    return {
                        value: this.current++,
                        done: false
                    }
                }
            }
        }
    }
}
console.log([...obj])
 
for (let val of obj) {
   console.log(val);
   
}

十四、泛型

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

函数泛型

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

使用泛型优化前:

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('独孤','求败')

使用泛型优化后:

function Add<T>(a: T, b: T): Array<T>  {
    return [a,b]
}
 
Add<number>(1,2)
Add<string>('1','2')

语法为函数名字后面跟一个<参数名> 参数名可以随便写 例如我这儿写了T

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

type使用泛型

type A<T> = string | number | T

let a: A<boolean> = true  // 无报错
let b: A<boolean> = '123'  // 无报错
let c: A<boolean> = 1233  // 无报错
let d: A<null> = null  // 无报错

接口使用泛型

interface Data<T> {
    msg: T
}

let data: Data<number> = {
    msg: 1
}

函数使用泛型

function add<T, K>(a: T, b: K): Array<T | K> {
    return [a, b]
}

当不传参。

在这里插入图片描述

当传入两个不同类型的实参,TS会自动进行类型推论。
在这里插入图片描述

实例:使用泛型封装一个axios请求

const axios = {
    get<T>(url: string): Promise<T> {
        return new Promise((resolve, rejects) => {
            let xhr: XMLHttpRequest = new XMLHttpRequest()
            xhr.open('GET', url)
            xhr.onreadystatechange = () => {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    resolve(JSON.parse(xhr.responseText))
                }
            }
            xhr.send(null)
        })
    }
}

interface Data {
    message: string
    code: number
}

axios.get<Data>('./data.json').then(res => {
    console.log(res);
})

运行结果:
在这里插入图片描述

泛型约束

在类型后面跟一个extends再跟一个约束的类型。

function add<T extends number>(a:T, b:T){
    return a + b
}

add(undefined, undefined)  // 报错
add(1, 2)  // 不报错

这里举一个例子,我们定义一个求数据长度的泛型函数如下:

function fn<T>(a: T) {
    a.length  // 报错
}

按上面的定义方式会报错,因为并不是所有类型都有length,而T表示未知类型,因此会报错。

这时,我们就能使用泛型约束来解决这个问题。我们可以定义一个泛型接口

interface Len {
    length: number
}

function fn<T extends Len>(a: T) {
    a.length
}

// T 继承于 约束Len,因此fn传入的实参必须包含length属性才符合约束条件。
fn('1111')  // '1111'字符串对象有length属性,因此不报错
fn([1, 2, 3])  // 数组有length属性,因此不报错
fn(121212)  // number类型没有length属性,因此会报错
fn(false)  // boolean类型没有length属性,因此会报错

keyof实现约束对象的key

keyof可以把对象中的key推断成联合类型。

let obj = {
    name: '小5',
    sex: '男'
}

type Key = keyof typeof obj

在这里插入图片描述

那么我们该如何实现一个传入对象obj,和其某个key,来返回对象obj中的key所对应的value呢?

let obj = {
    name: '小5',
    sex: '男'
}
// 使用keyof将对象中的key推断成联合类型: name | sex
type Key = keyof typeof obj
// 使用泛型约束限制传入的第一个参数是对象,第二个参数是此对象的key
function ob<T extends object, K extends keyof T>(obj: T, key: K) {
    return obj[key]
}

ob(obj, 'name')
console.log(ob(obj, 'name'));  // 小5
ob(obj, 'age')  // 报错,因为obj对象中没有名为age的key

keyof高级用法

下面我们使用泛型工具来实现约束Data中的属性全部都可选。

interface Data {
    name: string
    age: number,
    sex: string
}

type Options<T extends object> = {
    [Key in keyof T]?:T[Key]
}

type B = Options<Data>

在这里插入图片描述

十五、tsconfig.json配置文件

生成tsconfig.json文件

通过指令tsc --init

"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.strict

严格模式

9.module

默认common.js,可选es6模式、amd、umd等

十六、namespace命名空间

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

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

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

命名空间中通过export将想要暴露的部分导出,如果不用export导出是无法读取其值的。

namespace a {
    export const Time: number = 1000
    export const fn = <T>(arg: T): T => {
        return arg
    }
    fn(Time)
}
console.log(a.Time);  // 1000
console.log(a.fn(1000));  // 1000

嵌套命名空间

namespace a {
    export namespace b {
        export class Vue {
            parameters: string
            constructor(parammeters: string) {
                this.parameters = parammeters
            }
        }
    }
}

let v = a.b.Vue
const vue = new v('111')
console.log(vue.parameters);  // 111

抽离命名空间

a.ts

export namespace V {
    export const a = 1
}

b.ts

import {V} from '../observer/index'
 
console.log(V);  // { a: 1}

合并命名空间

namespace a {
    export const a = 1
}

namespace a {
    export const b = 1
}

// 等同于
// namespace a {
//     export const a = 1
//     export const b = 1
// }

在这里插入图片描述

十七、三斜线指令

三斜线指令是包含单个XML标签的单行注释。注释的内容会作为编译器指令使用。

三斜线指令仅可放在包含它的文件的顶端,一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。

如果三斜线指令出现在一个语句或声明之后,会被当作普通的单行注释,并且不具有特殊的含义。

/// <reference path="..." />指令是三斜线指令中最常见的一种,它用于声明文件间的依赖。

你可以把三斜线指令理解成import,它可以告诉编译器在编译过程中要引入的额外的文件。

注意:三斜线指令要在生成代码的模板标准为AMDSystem的情况下使用,因此要将tsconfig.json文件的module配置为AMDSystem,并且配置outFile的依赖文件路径,outFile配置路径可以将多个相互依赖的文件生成一个文件,一般用在AMD模块中。

例如:

a.ts

namespace A {
    export const fn = () => 'a'
}

b.ts

namespace A {
    export const fn2 = () => 'b'
}

index.ts

// 引入之后直接可以使用变量A
/// <reference path="a.ts" />
/// <reference path="b.ts" />
console.log(A.fn());
console.log(A.fn2());

输入指令tscts文件编译成js文件,并存放在outFile指定路径,并切换至outFile路径,使用node .\index.js运行文件,可以看到成功输出命名空的内容。

另外,在将ts文件编译成js文件之前也可以修改tsconfig.json的配置,将removeComments设为true,这样编译完成后的js文件将看不到注释和三斜线指令。

十八、声明文件d.ts

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

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

注意:CommonJS模板下才能使用声明文件d.ts,因此要在tsconfig.json文件中配置moduleCommonJS

假如我们安装了expressaxios第三方库,其中axios是自带d.ts声明文件的,可以在node_modules文件夹中的axios文件夹里找到,也可以import导入axios,按住ctrl键点击axios可以快速跳转至d.ts声明文件,而express是没有自带d.ts声明文件的,因此导入时会报错,如下图

在这里插入图片描述

提示告知了两种解决方案

  1. 通过指令npm i --save-dev @types/express安装它的声明文件。
  2. 自己添加一个包含declare module 'express'的新声明(.d.ts)文件。

类似的,许多第三方库下载下来是不包括声明文件的,我们都可以使用方法1来安装它们的声明文件,安装完成后可以在node_modules/@types文件夹内找到相应的.d.ts声明文件,如下

在这里插入图片描述

另外,有部分不知名的第三方库是无法通过方法1下载声明文件的,只能通过自定义声明文件。下面我们尝试手写一个express声明文件。

index.ts

import express from 'express'

const app = express()

const router = express.Router()

app.use('/api', router)

router.get('/list', (req, res) => {
    res.json({
        code: 200
    })
})
 
app.listen(9001,()=>{
    console.log(9001)
})

express.d.ts

declare module 'express' {
    interface Router {
        get(path: string, cb: (req: any, res: any) => void): void
    }
    interface App {
 
        use(path: string, router: any): void
        listen(port: number, cb?: () => void): void
    }
    interface Express {
        (): App
        Router(): Router
 
    }
    const express: Express
    export default express
}

关于这些第三方的声明文件包都收录在npm当中。

十九、Mixins混入

二十、装饰器Decorator

Decorator装饰器是一项实验性特性,在未来的版本中可能会发生改变。

装饰器不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能。若要启动实验性的装饰器特性,必须在命令行或tsconfig.json里启用编译器选项。experimentalDecorators:true

简单理解装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。

装饰器种类

  1. 类装饰器
  2. 方法装饰器
  3. 属性装饰器
  4. 参数装饰器

1.类装饰器

下面定义一个简单的类装饰器函数,并使用该装饰器

// 定义一个类装饰器函数 它会把Class Http的构造函数传入你的Base函数当作第一个参数
const Base: ClassDecorator = (target) => {
    console.log(target);  // [class Http]
    target.prototype.xiao5 = '小5'  // 通过装饰器给Http类增加属性
    target.prototype.fn = () => {  // 通过装饰器给Http类增加方法
        console.log('哎哟~尼赣麻~');
    }
}

// 定义一个简单的类,并通过 @函数名 的方式使用装饰器
@Base
class Http {
    constructor() {

    }
    // ...
}
const http = new Http() as any
console.log(http.xiao5);  // 小5
http.fn();  // 哎哟~尼赣麻~

可以看到我们可以不去破坏Http类的结构,从而给Http类增加方法和属性,起到了修饰的作用,因此叫做装饰器或修饰器

装饰器工厂

我们可以通过闭包实现给装饰器传递参数,其实也就是一个高阶函数:外层的函数接收值,里层的函数最终接收类的构造函数。

// 定义一个类装饰器函数 它会把Class Http的构造函数传入你的Base函数当作第一个参数
const Base = (name: string) => {
    const fn: ClassDecorator = (target) => {
        // target是原型对象
        console.log(target);  // [class Http]
        target.prototype.xiao5 = '小5'
        target.prototype.fn = () => {
            console.log('哎哟~尼赣麻~');
        }
        target.prototype.getName = () => {
            console.log(name);  // 使用装饰器出来的参数
        }
    }
    return fn
}

// 定义一个简单的类,并通过 @装饰器名 的方式使用装饰器
@Base('xiao 5')
class Http {
    constructor() {

    }
    // ...
}
const http = new Http() as any
console.log(http.xiao5);  // 小5
http.fn();  // 哎哟~尼赣麻~
http.getName() // xiao 5

2.方法装饰器

下面定义一个简单的get请求的方法装饰器

import axios from 'axios'

// 定义一个get请求相关的方法装饰器
const Get = (url: string) => {
    const fn: MethodDecorator = (target,propertyKey,descriptor:PropertyDescriptor) => {
        console.log(target);  // {}
        console.log(propertyKey);  // getList
        console.log(descriptor);  // {value: [Function: getList], writable: true, enumerable: false, configurable: true}
        实现get请求
        axios.get(url).then(res => {
            descriptor.value(res.data)
        })
    }
    return fn
}

// 定义一个简单的类,并通过 @装饰器名 的方式使用装饰器
class Http {
    @Get('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10')
    getList(data: any) {
        console.log(data);
    }
    create () {}

}
const http = new Http() as any

方法装饰器返回三个参数:

  1. target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  2. propertyKey:成员的名字。
  3. descriptor:成员的属性描述符

3.参数装饰器

下面实现一个简单的参数装饰器

安装反射器第三方库:npm i reflect-metadata

导入反射器:import 'reflect-metadata'

import axios from 'axios'
import 'reflect-metadata'
// 定义一个get请求相关的方法装饰器
const Get = (url: string) => {
    const fn: MethodDecorator = (target, propertyKey, descriptor: PropertyDescriptor) => {
        // 根据名字key从反射器取result
        const key = Reflect.getMetadata('key', target)
        // 实现get请求
        axios.get(url).then(res => {
            descriptor.value(key ? res.data[key] : res.data)
        })
    }
    return fn
}

// 定义一个简单的参数装饰器,直接取出请求响应的结果res中的result
const Result = () => {
    const fn: ParameterDecorator = (target, propertyKey,parameterIndex) => {
        console.log(target);  // {}
        console.log(propertyKey);  // getList
        console.log(parameterIndex);  // 0
        // 将 result 存入 反射器,取名key
        Reflect.defineMetadata('key', 'result', target)
    }
    return fn
}

// 定义一个简单的类,并通过 @装饰器名 的方式使用装饰器
class Http {
    @Get('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10')
    getList(@Result() data: any) {
        console.log(data);  // 直接输出data.result
    }
    create () {}
}
const http = new Http() as any

参数装饰器返回三个参数:

  1. target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  2. propertyKey:成员的名字。
  3. parameterIndex:参数在函数参数列表中的索引。

4.属性装饰器

二一、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

安装完成,并修改配置完成后的package.json文件

{
  "name": "rollupTs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "cross-env NODE_ENV=development  rollup -c -w",
    "build":"cross-env NODE_ENV=produaction  rollup -c"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "cross-env": "^7.0.3",
    "rollup-plugin-livereload": "^2.0.5",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-replace": "^2.2.0",
    "rollup-plugin-serve": "^1.1.0",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.31.1",
    "typescript": "^4.5.5"
  }
}

配置rollup.config.js文件,此文件由自己添加。

// console.log(process.env);
import path from "path";
import ts from "rollup-plugin-typescript2";
import serve from "rollup-plugin-serve";
import livereload from "rollup-plugin-livereload";
import { terser } from "rollup-plugin-terser";
import replace from "rollup-plugin-replace";
const isDev = () => {
  return process.env.NODE_ENV === "development";
};
// 使用rollup构建ts项目
export default {
  // 入口文件路径
  input: "./src/index.ts",
  // 出口文件路径
  output: {
    file: path.resolve(__dirname, "./lib/index.js"),
    // UMD叫做通用模块定义规范(Universal Module Definition),它可以通过运行时或者编译时
    // 让同一个代码模块在使用CommonJS、CMD甚至是AMD的项目中运行。
    format: "umd",
    // Source Map就是一个信息文件,里面存储了代码打包转换后的位置信息,实质是一个json描述文件,
    // 维护了打包前后的代码映射关系,可以快速找到报错的具体位置以及原因
    sourcemap: true,
  },
  // 插件
  plugins: [
    // 会自动读取tsconfig.json
    ts(),
    // 开启热更新
    isDev() && livereload(),
    // 开启代码压缩,代码会被压缩成一行
    terser({
      compress: {
        // 自动删除console.log()
        drop_console: true,
      },
    }),
    // 将查看当前生产环境的变量注册到全局使用
    replace({
      "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
    }),
    // 开启serve

    isDev() &&
      serve({
        open: true,
        port: 1988,
        openPage: "/public/index.html",
      }),
  ],
};

配置tsconfig.json文件,此文件由tsc --init生成。

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    "module": "ES2015",                                /* Specify what module code is generated. */
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "moduleResolution": "node10",                     /* Specify how TypeScript looks up a file from a given module specifier. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
    // "resolveJsonModule": true,                        /* Enable importing .json files. */
    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}

npm run dev启动就可以尽情玩耍了

在这里插入图片描述

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

安装完成,并修改配置后的package.json文件

{
  "name": "webpack_ts_project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "html-webpack-plugin": "^5.5.1",
    "ts-loader": "^9.4.2",
    "typescript": "^5.0.4",
    "webpack": "^5.82.1",
    "webpack-cli": "^5.1.1",
    "webpack-dev-server": "^4.15.0"
  }
}

配置文件webpack.config.js,此文件由自己手动创建

const path = require("path");
const htmlwebpackplug = require("html-webpack-plugin");

module.exports = {
  // 入口
  entry: "./src/index.ts",
  // 项目模式
  mode: "development",
  // 出口
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "index.js",
  },
  module: {
    // 匹配规则
    rules: [
      {
        test: /\.ts$/,
        use: "ts-loader",
      },
    ],
  },
  // server
  devServer: {
    port: 1988,
    proxy: {},
  },
  resolve: {
    // 扩展,引入的时候不用追加后缀
    extensions: [".js", ".ts"],
  },
  // 插件
  plugins: [
    // 热更新
    new htmlwebpackplug({
      template: "./public/index.html",
    }),
  ],
};

npm run dev启动就可以尽情玩耍了

在这里插入图片描述

二三、esbuild+swc构建TS项目

esbuild

前端工具层出不穷,之前有常用的打包工具webpack,现在有了速度更快的vite。vite的开发模式是基于esBuild编译的,打包又是基于rollup,启动项目是很快的。

在esbuild的官网介绍中,打包threejs只需要0.37秒,下面是esbuild与其他打包工具所花费的事件对比图。

img

esbuild是go语言编写的,并且是多线程执行,性能是js的好几十倍,所以很快。

esbuild的优点

  1. 无需缓存即可实现基础打包
  2. 支持ES6跟CommonJS模块
  3. 支持ES6 Tree Shaking
  4. 体积小
  5. 插件化
  6. 内置支持编译jsx
  7. 其他

SWC

SWC宣传其比Babel快20倍(四核情况下可以快70倍),Babel是处理浏览器兼容性问题,比如ES6语法转ES5语法,SWC使是用rust写的,所实现的功能和Babel一样,但是速度比Babel更快。

二四、实战:使用TS编写发布订阅模式

什么是发布订阅模式,其实小伙伴已经用到了发布订阅模式例如addEventListenerVue evnetBus

思维导图

首先,需要定义三个角色,发布者,订阅者,调度者。

在这里插入图片描述

环境配置:

配置二一节所讲的Rollup构建Ts项目所需的环境,记得在rollup.config.js中取消自动删除console.log()的配置。

具体代码:

on:订阅/监听/绑定

emit:发布/派发

once:只订阅/监听/绑定一次

off:解绑/取消订阅/取消监听

interface Evenet {
    // 监听
    on: (name:string, fn:Function) => void
    // 派发
    emit: (name:string, ...args:Array<any>) => void,
    // 删除
    off: (name: string, fn:Function) => void,
    // 只执行一次
    once: (name:string, fn:Function) => void
}

interface List {
    [key:string] : Array<Function>
}
// 调度中心类
class Dispatch implements Evenet {
    // list用于存储事件,每条事件数据的格式都是 事件名:事件回调
    list: List
    constructor() {
        this.list = {}
    }
    // 监听事件,实例化类后,新增一个on事件(调一次类中的on方法)就向list添加一个事件(事件名,回调函数)
    on(name:string, fn:Function) {
        const callback = this.list[name] || []
        callback.push(fn)
        this.list[name] = callback
        // 输出事件列表
        // console.log(this.list);
    }
    // 派发事件,根据list里面存储的事件的事件名触发,并传入非固定的实参。
    emit(name: string, ...args: Array<any>) {
        // 根据事件名获取事件回调
        let eventName = this.list[name]
        // 如果事件名存在[也就是回调存在]
        if (eventName) {
            eventName.forEach(fn => {
                // apply方法将数组入参变为一般入参,也就是将中括号去掉再作为参数
                fn.apply(this, args)  // 使用apply方法将emit派发时传递的参数传给事件回调
            })
        } else {
            // 事件名不存在
            console.error(`名称错误${name}`)
        }
    } 
    off(name: string, fn:Function) {
        let eventName = this.list[name]
        if (eventName && fn) {
            let index = eventName.findIndex(fns => fns === fn)
            eventName.splice(index, 1)
            // console.log(eventName);
        } else {
            console.error(`名称错误${name}`);
        }
    }
    once(name:string, fn:Function) {
        let temp = (...args: Array<any>) => {
            fn.apply(this, args)
            this.off(name, temp)
        }
        this.on(name, temp)
    }
}

// 实例化Dispatch类
const o = new Dispatch()

// 监听/绑定事件post
o.on('post', (...args: Array<any>) => {
    console.log('post1');
    
})
// 监听/绑定事件post,回调不同的post
const fn = (...args: Array<any>) => {
    console.log('post2');
}
o.on('post', fn)
// 只执行一次事件post
o.once('post', (...args: Array<any>) => {
    console.log(args, 'once');
})
// 触发/派发事件post
o.emit('post', 1, false, { name: '小5' })
o.emit('post', 2, true, { name: '小5' })
// 删除/接触绑定事件post
// o.off('post', fn)

二五、Set、Map、weakSet、weakMap

在ES5的时候,常用Array、Object,在ES6又新增了两个类型,Set和Map,类似于数组和对象。

Set

集合是由一组无序且唯一(即不能重复)的项组成的,可以用来过滤重复元素。

属性:

  1. size:返回集合所包含的元素个数

操作:

  1. add(value):添加某个值,返回Set结构本身。
  2. delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  3. has(value):返回一个布尔值,表示该值是否为Set的成员。
  4. clear():清除所有成员,无返回值。

去重

let arr = [...new Set([1, 1, 2, 3, 5, 5])]
console.log(arr);  // [ 1, 2, 3, 5 ]

Map

Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当做键,是一种更加完善的Hash结构实现,如果你需要“键值对”的数据结构,Map比Object更合适。

操作方法同Set

let obj = { name: "小5" }

let map: Map<object, any> = new Map()
// set
map.set(obj, '小5') 
console.log(map);  // Map(1) { { name: '小5' } => '小5' }
// get
console.log(map.get(obj));  // 小5
// has
console.log(map.has(obj));  // true
// size
console.log(map.size);  // 1
// delete
map.delete(obj)
console.log(map);  // Map(0) {}

WeakSe和WeakMap

WeakSet和WeakMap的键都是弱引用,不会被计入垃圾回收,我们来演示一下。

首先obj引用了这个对象 + 1,aahph也引用了 + 1,wmap也引用了,但是不会 + 1,应为他是弱引用,不会计入垃圾回收,因此 obj 和 aahph 释放了该引用 weakMap 也会随着消失的,但是有个问题你会发现控制台能输出,值是取不到的,因为V8的GC回收是需要一定时间的,你可以延长到500ms看一看,并且为了避免这个问题不允许读取键值,也不允许遍历,同理weakSet 也一样。

let obj:any = {name:'小满zs'} //1
let aahph:any = obj //2
let wmap:WeakMap<object,string> = new WeakMap()
 
wmap.set(obj,'爱安徽潘慧') //2 他的键是弱引用不会计数的
 
obj = null // -1
aahph = null;//-1
//v8 GC 不稳定 最少200ms
 
setTimeout(()=>{
    console.log(wmap)
},500)

二五、TS进阶用法Partial&Pick

Partial和Pick是TypeScript内置高级类型。

Partial

Partial实现将T中的所有属性设置为可选。

源码解读:

type Partial<T> = {
    // keyof 将对象的所有key组合并推断成联合类型
    // in 遍历 联合类型中的所有类型并赋值给 P
    [P in keyof T]?: T[P]
}

使用:

type Person = {
    name: string,
    age: number,
    text: string
}

type p = Partial<Person>
// 此时,p就等同于
// type p = {
//     name?: string,
//     age?: number,
//     text?: string
// }

Pick

Pick实现从类型定义T的属性中选取指定一组属性,返回一个新的类型定义。

源码解读:

// K extends keyof T表示 K类型 必须满足 T类型的所有key所组成的联合类型
type Pic<T, K extends keyof T> = {
    [P in K]: T[P]
}

使用:

type Person = {
    name: string,
    age: number,
    text: string,
    address: string
}

type Ex = "text" | "age"

type A = Pick<Person, Ex>
// 此时A等同于
// type A = {
//     text: string,
//     age: number
// }
// namespace A

二六、TS进阶用法Record&Readonly

Readonly

Readonly和我们上节学的Partial很像,只是把?替换成了readonly

Readonly实现将T中的所有属性设置为可选。

源码解读:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

使用:

type Person = {
    name: string,
    age: number,
    text: string
}

type p = Readonly<Person>
// 此时,p就等同于
// type p = {
//    readonly name: string,
//    readonly age: number,
//    readonly text: string
// }

Record

Record实现了既约束key,又约束value。

源码解读:

// keyof any 会返回 type key = string | number | symbol
// K extends keyof any表示:K 必须是 string或number或symbol
type Record<K extends keyof any, T> = {
    [P in K]: T
}

使用:

// 定义key约束
type K = "A" | "B" | "C"

// 定义value约束
type Person = {
    name: string,
    age: number,
    text: string
}

type p = Record<K, Person>
// 此时,p就等同于
// type p = {
//     A: Person,
//     B: Person,
//     C: Person
// }

// 定义B类型的一个对象
let obj: B = {
    A: {name: '小5', age: 18, text: "尼赣嘛"},
    B: {name: '小5', age: 18, text: "尼赣嘛"},
    C: {name: '小5', age: 18, text: "尼赣嘛"},
}

二七、TS进阶用法infer

infer是TypeScript新增的关键字,充当占位符

1.infer占位符的简单使用

我们来实现一个条件类型推断的例子

定义一个类型 如果是数组类型 就返回 数组元素的类型 否则 就传入什么类型 就返回什么类型

常规写法:

// 常规写法
// T[number]中的number用于索引T类型
type TYPE<T> = T extends Array<any> ? T[number] : T

type A = TYPE<Array<string>>  // 等同于 type A = string
type B = TYPE<Array<number>>  // 等同于 type B = number
type C = TYPE<string>  // 等同于 type C = string
type D = TYPE<boolean>  // 等同于 type D = boolean

使用占位符的写法:

// 使用infer占位符
// infer充当占位符,在这里: Array<infer U>中使用infer后,U将表示数组元素的类型,并且后续在表达式后面可以使用U表示此类型
type TYPE<T> = T extends Array<infer U> ? U : T

type A = TYPE<Array<string>>  // 等同于 type A = string
type B = TYPE<Array<number>>  // 等同于 type B = number
type C = TYPE<string>  // 等同于 type C = string
type D = TYPE<boolean>  // 等同于 type D = boolean

2.infer类型提取

提取头部元素

type Arr = ['a', 'b', 'c']

type First<T extends any[]> = T extends [infer First, ...any[]] ? First : []

type a = First<Arr>
// 此时等价
// type a = "a"

类型参数 T 通过extends 约束 只能是数组类型,然后通过infer 声明局部 First 变量做提取,后面的元素可以是任意类型,然后把局部变量返回。

提取尾部元素

type Arr = ['a', 'b', 'c']

type Last<T extends any[]> = T extends [...any[], infer Last] ? Last : []

type a = Last<Arr>
// 此时等价
// type a = "c"

反过来。

剔除第一个元素Shift

type Arr = ['a', 'b', 'c']

type First<T extends any[]> = T extends [unknown, ...infer Rest] ? Rest : []

type a = First<Arr>
// 此时等价
// type a = ["b", "c"]

剔除最后一个元素pop

type Arr = ['a', 'b', 'c']

type Last<T extends any[]> = T extends [unknown, ...infer Rest] ? Rest : []

type a = Last<Arr>
// 此时等价
// type a = ["a", "b"]

3.infer递归

实现[1, 2, 3, 4]—>[4, 3, 2, 1]

type Arr = [1, 2, 3, 4]

type ReverArr<T extends any[]> = T extends [infer First, ...infer Rest] ? [...ReverArr<Rest>, First] : T

type Arrb = ReverArr<Arr>
// 此时等价于
// type Arrb = [4, 3, 2, 1]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值