TypeScript

121 阅读11分钟

npm install -g typescript

使用 tsc ./01.ts 进行编译

vscode 自动编译ts配置

  1. 第一步

tsc --init 会在目录中生成一个 tsconfig.json文件

  1. 第二部

终端->执行命令 tsc --watch; 然后编辑的每个ts文件都会转为相同名字的js文件

js和ts类型总览

  1. JavaScript中的数据类型

string, number, boolean, null, undefined, bigint, symbol, object, 备注:其中object包含:Array、Function、Date、Error等......

  1. TypeScript中的数据类型
  1. 上述javaScript中的所有类型

  2. 六个新类型: any、unknown、never、void、tuple、enum

  3. 两个用于自定义类型的方式 type、interface

注意点

在JavaScript中的这些内置构造函数:Number、String、Boolean, 它们用于创建对应的包装对象,在日常开发时很少使用,在TypeScript中也是同理,所以在TypeScript中进行类型声明时,通常都要用小写的number、string、boolean

例如下面的代码:

let str1: string
str1 = 'hello'
str1 = new String('hello') //报错

let str2: String
str2 = 'hello'
str2 = new String('hello')

console.log(typeof str1) //string
console.log(typeof str2) //object
  1. 原始类型VS包装对象
  • 原始类型:如number、string、boolean,在JavaScript中是简单数据类型,它们在内存中占用空间少,处理速度快
  • 包装对象:如Number对象、String对象、Boolean对象,是复杂类型,在内存中占用更多空间,在日常开始时很少由开发人员自己创建包装对象。
  1. 自动装箱:JavaScript在必要时会自动将原始类型包装成对象,以便调用方法或访问属性
let str = 'hello';

// 当访问str.length时,JavaScript引擎做了以下工作,
let size = (function () {
    // 1.自动装箱:创建一个临时的String对象包装原始字符串
    let tempStringObject = new String(str);
    // 2.访问String对象的length属性
    let lengthValue = tempStringObject.length;
    // 3.销毁临时对象,返回长度值
    return lengthValue;
})()

console.log(size)

常用类型

  1. any
    从 TypeScript 到 AnyScript 的进化;

any的含义:任意类型,一旦将变量类型限制为any,那就意味着放弃了对该变量的类型检查

  1. unknown

unknown可以理解为一个类型安全的any, 适用于:不确定数据的具体类型

  1. never

never的含义是:任何值都不是,简而言之就是不能有值,undefined、null、''、0都不行! never一般是ts主动推断出来的

function demo():never{
    throw new Error('程序异常')
}
  1. void

void 通常用于函数返回值声明,含义:【函数不返回任何值,调用者也不应该依赖其返回值进行任何操作】

  1. object

关于objectObject, 实际开发中用的相对较少,因为范围太大了。

object(小写)的含义是:所有非原始类型,可存储:对象、函数、数组等,由于限制的范围比较宽泛,在实际开发中使用的相对较少。

let a: object //a能存储的类型是【非原始类型】

a = {}
a = { name: 'tom' }
a = [1, 2, 3, 4]
a = function () { }
a = new String('123')
class Person { }
a = new Person()

// 以下代码,是将【原始类型】赋值给a,有警告
a = 1
a = true
a = '你好'
a = null
a = undefined

let b: Object //b能存储的类型是可以调用到Object方法的类型

b = {}
b = { name: 'tom' }
b = [1, 2, 3, 4]
b = function () { }
b = new String('123')
class Person { }
b = new Person()

b = 1
b = true
b = '你好'

// 以下代码,有警告
b = null
b = undefined

声明对象类型

let person: {
    name: string;
    age?: number;
    [key: string]: any; //索引签名
}

声明函数类型

let count: (a: number, b: number) => number;
count = function (a, b) {
    return a + b;
}

声明数组类型

let arr1: string[]
let arr2: Array<string>

arr1 = ['1','2']
arr2 = ['1','2']

  1. tuple
let arr1:[string,number];
let arr2:[string,boolean?];
let arr3:[number,...string[]];

arr1 = ['hello',21];

arr2 = ['hello'];
arr2 = ['hello',false];

arr3 = [100,'a','b','c'];
arr3 = [100];
  1. enum

枚举(enum)可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护

// 数字枚举
enum Direction {
    Up,
    Down,
    Left,
    Right
}
function walk(data: Direction) {
    if (data === Direction.Up) {
        console.log('up')
    } else if (data === Direction.Down) {
        console.log('down')
    } else if (data === Direction.Left) {
        console.log('left')
    } else if (data === Direction.Right) {
        console.log('right')
    } else {
        console.log('unknow')
    }
}
walk(Direction.Left)
//字符串枚举
enum Direction {
    Up = "Up",
    Down = "Down",
    Left = "Left",
    Right = "Right"
}
console.log(Direction.Down)
  1. type

type 可以为任意类型创建别名,让代码更简洁,可读性更强,同时更方便地进行类型复用和扩展

//联合类型
type Status = number | string
//交叉类型
type Area = {
    height:number;
    wight:number;
}
type Address = {
    num:number; 
    cell:number;
    room:string;
}

type House = Area & Address
  1. 一个特殊情况
定义函数的时候直接定义返回值为void
function test():void{
    return undefined;
    // 以下全部报错
    return 1;
    return '';
    return null;
    return []
}
type FunType = () => void;

const test:FunType = ()=>{
    // 此处不报错
    return 999;
}
// 原因就是为了和以下代码不冲突
const src = [1,2,3]
const dst = [0]

src.forEach(el => dst.push(el))
  1. 复习类的相关知识
class Person {
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    speak() {
        console.log(`我叫${this.name}, 今年${this.age}岁`)
    }
}
class Student extends Person {
    grade:string
    constructor(name:string,age:number,grade:string){
        super(name,age);
        this.grade = grade;
    }
    study(){
        console.log(`${this.name}正在学习`)
    }
    // 重新父类的方法
    override speak() {
        console.log(`我是学生我叫${this.name}, 今年${this.age}岁`)
    }
}

let s1 = new Student('zhangsan',18,'高三');
console.log(s1);

  1. 属性修饰符
修饰符含义具体规则
public公开的可以被:类内部,子类,类外部 访问
protected受保护的可以被:类内部,子类 访问
private私有的可以被:类内部 访问
readonly只读属性属性无法修改

属性的简写形式

class Person {
    constructor(public name: string, public age: number) { }
    speak() {
        console.log(`我叫${this.name}, 今年${this.age}岁`)
    }
}
class Student extends Person {
    constructor(name: string, age: number, public grade: string) {
        super(name, age);
    }
    study() {
        console.log(`${this.name}正在学习`)
    }
    // 重新父类的方法
    override speak() {
        console.log(`我是学生我叫${this.name}, 今年${this.age}岁`)
    }
}

let s1 = new Student('zhangsan', 18, '高三');
console.log(s1);

readonly修饰符

class Person {
    constructor(public name: string, public readonly age: number) { }
    speak() {
        console.log(`我叫${this.name}, 今年${this.age}岁`)
    }
}
  1. 抽象类
  • 概述:抽象类是一种无法被实例化的类,专门用来定义类的机构和行为,类中可以写抽象方法,也可以写具体实现。抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法
  • 简记:抽象类不能实例化,其意义是可以被继承,抽象类里可以有普通方法,也可以有抽象方法。

abstract class Package {
    // 构造方法
    constructor(public weight: number) { }
    // 抽象方法
    abstract calculate(): number
    // 具体方法
    printPackage() {
        console.log(`包裹的重量为${this.weight},运费为${this.calculate()}元`)
    }
}

class StandardPackage extends Package{
    constructor(weight:number,public unitPrice:number){
        super(weight);
    }
    calculate(): number {
        return this.weight * this.unitPrice;
    }
}
let s1 = new StandardPackage(10,5);
s1.printPackage()
  1. interface (接口)

interface 是一种定义结构的方式,主要作用为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意interface只能定义格式,不能包含任何实现

  • 定义类的结构
interface PersonInterface {
    name: string;
    age: number;
    speak(n: number): void;
}

class Person implements PersonInterface {
    constructor(public name: string, public age: number) { }
    speak(n: number) {
        for (let i = 0; i < n; i++) {
            console.log(`你好, 我叫${this.name}, 我的年龄是${this.age}`)
        }
    }
}

let p1 = new Person('zhangsna',18);
p1.speak(1);
  • 定义对象结构
interface UserInterface {
    name: string;
    readonly gender: string;
    age?: number;
    run: (n: number) => void
}

const user: UserInterface = {
    name: 'zhangsan',
    gender: '男',
    age: 18,
    run: (n) => {
        console.log(n)
    }
}
  • 定义函数结构
interface CountInterface {
    (a: number, b: number): number
}

const count: CountInterface = (x, y) => {
    return x + y
}
  • 接口之间的继承
interface PersonInterface {
    name: string;
    age: number;
}
interface StudentInterface extends PersonInterface {
    grade: string;
}

const stu: StudentInterface = {
    name: 'zhangsan',
    age: 19,
    grade: '四年级'
}
  • 接口和抽象类

一个类可以实现多个接口

interface FlyInterface {
    fly(): void;
}
interface SwimInterface {
    swim(): void;
}
class Duck implements FlyInterface, SwimInterface {
    fly(): void {

    };
    swim(): void {

    }
}

一个类只能继承一个抽象类

泛型

泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时任然保持类型的安全性。

function logData<T>(data: T) {
    console.log(data)
}

logData<number>(123);
logData<string>('hello')
function logData<T, U>(data1: T, data2: U): T | U {
    return Date.now() % 2 ? data1 : data2
}

logData<number, boolean>(123, true);
logData<string, number>('hello', 456)

泛型接口

interface PersonInterface<T> {
    name: string;
    age: number,
    extraInfo: T
}

type JobInfo = {
    title:string;
    company:string;
}

let p:PersonInterface<JobInfo> = {
    name:'zhangsan',
    age:18,
    extraInfo:{
        title:'高级开发工程师',
        company:'发发发科技有限公司'
    }
}

泛型类

class Person<T> {
    constructor(public name: string, public age: number, public extraInfo: T) { }
    speak() {
        console.log(this.extraInfo)
    }
}

const p1 = new Person<number>('zhangsan', 18, 500);

装饰器

  1. 装饰器本质是一种特殊的函数,它可以对: 类、属性、方法、参数进行扩展,同时能让代码更简洁。
  2. 截止目前,装饰器依然是实验性特性,需要开发者手动调整配置,来开启装饰器支持
  3. 装饰器有5种:
  • 类装饰器
  • 属性装饰器
  • 方法装饰器
  • 访问器装饰器
  • 参数装饰器

建议使用 "experimentalDecorators": true 配置来开启装饰器支持

类装饰器

1. 基本语法

类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑。

function Demo(target: Function) {
    console.log('target', target)
}

@Demo
class Person {
    constructor(public name: string, public age: number) { }
}

2. 应用举例

需求:定义一个装饰器,实现Person实例调用toString时返回JSON.stringify的执行结果。


function CustomString(target: Function) {
    target.prototype.toString = function () {
        return JSON.stringify(this)
    }
}

@CustomString
class Person {
    constructor(public name: string, public age: number) { }
}
const p1 = new Person('zhangsan', 18);
console.log(p1.toString())

3. 关于返回值

类装饰器有返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类。

类装饰器无返回值:若类装饰器无返回值或返回undefined, 那被装饰的类不会被替换

function Demo(target: Function) {
    return class {
        test() {
            console.log(200)
            console.log(300)
            console.log(400)
        }
    }
}

@Demo
class Person {
    test() {
        console.log(100)
    }
}
console.log(Person)

4. 关于构造类型

在TypeScript中,Function类型所表示的范围十分广泛,包括:普通函数、箭头函数、方法等等。但并非Function 类型的函数都可以被new关键字实例化,例如箭头函数是不能被实例化的,那么TypeScript中该如何声明一个构造类型呢?有以下两种方式

  • 仅声明构造类型
type Constructor = new (...args:any[]) => {}

function test(fn:Constructor) {}

// const Person = ()=>{}
class Person {

}
test(Person)
  • 声明构造类型 + 指定静态属性
type Constructor = {
    new(...args: any[]): {},
    wife: string
}

function test(fn: Constructor) { }

// const Person = ()=>{}
class Person {
    static wife: string
}
test(Person)

5. 替换被装饰的类

对于高级一些的装饰器,不仅仅是覆盖一个原型上的方法,还要有更多功能,例如添加新的方法和状态。

需求:设计一个LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,在添加一个方法用于读取创建时间

type Constructor = new (...args: any[]) => {}

function LogTime<T extends Constructor>(target: T) {
    return class extends target {
        createdTime: Date
        constructor(...args: any[]) {
            super(...args)
            this.createdTime = new Date()
        }
        getTime() {
            return this.createdTime
        }
    }
}

@LogTime
class Person {
    constructor(public name: string, public age: number) { };
    speak() {
        console.log('你好呀')
    }
}

interface Person {
    getTime(): void
}

const p1 = new Person('zhangsan', 18);
console.log(p1)
console.log(p1.getTime())

装饰器工厂

装饰器工厂是一个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活地控制装饰器的行为。

需求:定义一个LogInfo装饰器工厂,实现Person实例可以调用到introduce方法,且introduce中输出内容的次数,由LogInfo接收的参数决定,

type Constructor = new (...args: any[]) => {}

function LogInfo<T extends Constructor>(n: number): Function {
    return function (target: T) {
        target.prototype.introduce = function () {
            for (let i = 0; i < n; i++) {
                console.log(`我叫${this.name},我今年${this.age}岁`)
            }

        }
    }
}

@LogInfo(5)
class Person {
    constructor(public name: string, public age: number) { };
    speak() {
        console.log('你好呀')
    }
}
interface Person {
    introduce(): void
}
const p1 = new Person('zhangsan', 18);
p1.introduce()

装饰器组合

装饰器可以组合使用,执行顺序为:先【由上到下】的执行所有的装饰器工厂,一次获取到装饰器,然后再【由下到上】执行所有的装饰器

装饰器组合 -- 执行顺序

type Constructor = new (...args: any[]) => {}

function test1<T extends Constructor>(target: T) {
    console.log('test1')
}
function test2() {
    console.log('test2工厂')
    return function <T extends Constructor>(target: T) {
        console.log('test2')
    }

}
function test3() {
    console.log('test3工厂')
    return function <T extends Constructor>(target: T) {
        console.log('test3')
    }
}
function test4<T extends Constructor>(target: T) {
    console.log('test4')
}

@test1
@test2()
@test3()
@test4
class Person{}

装饰器组合 -- 应用

type Constructor = new (...args: any[]) => {}
interface Person {
    introduce(): void,
    getTime(): Date
}
// 装饰器
function CustomString<T extends Constructor>(target: T) {
    target.prototype.toString = function () {
        return JSON.stringify(this)
    }
    // 封闭原型对象,禁止随意操作原型
    Object.seal(target.prototype)
}

// 装饰器工厂
function LogInfo(n: number): Function {
    return function <T extends Constructor>(target: T) {
        target.prototype.introduce = function () {
            for (let i = 0; i < n; i++) {
                console.log(`我叫${this.name}, 我今年${this.age}岁了`)
            }
        }
    }
}

// 装饰器
function LogTime<T extends Constructor>(target: T) {
    return class extends target {
        createdTime: Date;
        constructor(...args: any[]) {
            super(...args);
            this.createdTime = new Date();
        }
        getTime() {
            return this.createdTime
        }
    }
}

@CustomString
@LogInfo(5)
@LogTime
class Person {
    constructor(public name: string, public age: number) { };
    speak() {
        console.log('你好呀')
    }
}

const p1 = new Person('zhangsan', 18);
p1.speak();
console.log(p1.toString());
p1.introduce()
console.log(p1.getTime());

属性装饰器

1. 基本语法


/**
 * target: 对于静态属性来说是类,对于实例属性来说值是类的原型对象
 * propertyKey:属性名
*/

function Demo(target:object,propertyKey:string){
    console.log(target,propertyKey)
}

class Person {
    @Demo name: string
    @Demo age: number
    @Demo static school: string
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

2. 应用举例

需求:定义一个State属性装饰器,来监视属性的修改


/**
 * target: 对于静态属性来说是类,对于实例属性来说值是类的原型对象
 * propertyKey:属性名
*/

function State(target: object, propertyKey: string) {
    let key = `__${propertyKey}`
    Object.defineProperty(target, propertyKey, {
        get() {
            return this[key]
        },
        set(val) {
            this[key] = val
        },
        enumerable: true,
        configurable: true
    })
}

class Person {
    name: string
    @State age: number
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const p1 = new Person('zhangsan', 18);
const p2 = new Person('lisi', 19);
p1.age = 30;
p2.age = 50;
console.log(p1);
console.log(p2);

方法装饰器

基本语法

/**
 * target:对于静态方法来说值是类,对于实例方法来说值是原型对象
 * propertyKey:方法的名称
 * descriptor: 方法的描述对象,其中value属性是被装饰的方法
 * 
*/

function Demo(target:object,propertyKey:string,descriptor:object){
    console.log(target)
    console.log(propertyKey)
    console.log(descriptor)
}

class Person {
    constructor(public name: string, public age: number) { }
    @Demo
    speak() {
        console.log(`我的名字:${this.name}, 我的年龄:${this.age}`)
    }
    static isAdult(age: number) {
        return age >= 18;
    }
}

应用举例

需求

  1. 定义一个Logger方法装饰器,用于在方法执行前和执行后,均追加一些额外逻辑。
  2. 定义一个Validate方法装饰器,用于验证数据
/**
 * target:对于静态方法来说值是类,对于实例方法来说值是原型对象
 * propertyKey:方法的名称
 * descriptor: 方法的描述对象,其中value属性是被装饰的方法
 * 
*/

function Logger(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
    const originnal = descriptor.value
    descriptor.value = function (...args: any[]) {
        console.log(`${propertyKey}开始了`)
        const result = originnal.call(this, ...args)
        console.log(`${propertyKey}结束了`)
        return result
    }
}

function Validate(maxValue: number) {
    return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
        // 保存原始方法
        const original = descriptor.value;
        descriptor.value = function (...args: any[]) {
            // 自定义验证逻辑
            if (args[0] > maxValue) {
                throw new Error('年龄非法!')
            }
            return original.apply(this,args)
        }
    }
}

class Person {
    constructor(public name: string, public age: number) { }
    @Logger
    speak(str: string) {
        console.log(`我的名字:${this.name}, 我的年龄:${this.age},${str}`)
    }
    @Validate(120)
    static isAdult(age: number) {
        return age >= 18;
    }
}

const p1 = new Person('tom', 18);
p1.speak('123');

let res = Person.isAdult(90)
console.log('res',res)


访问器装饰器

基本语法

/**
 * target:对于静态方法来说值是类,对于实例方法来说值是原型对象
 * propertyKey:方法的名称
 * descriptor: 方法的描述对象,其中value属性是被装饰的方法
 * 
*/

function Demo(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(target)
    console.log(propertyKey)
    console.log(descriptor)

}

class Person {
    @Demo
    get address() {
        return '北京宏福科技园'
    }
    @Demo
    static get country() {
        return '中国'
    }
}

应用举例

需求 对Weather类的temp属性的set访问器进行限制,设置最低温度 -50,最高温度 50

function RangeValidate(min: number, max: number) {
    return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalSetter = descriptor.set;
        descriptor.set = function (value: number) {
            if (value < min || value > max) {
                throw new Error(`${propertyKey}的值应该在${min}${max}之间!`)
            }

            if (originalSetter) {
                originalSetter.call(this, value)
            }
        }
    }
}

class Weather {
    private _temp: number;
    constructor(_temp: number) {
        this._temp = _temp;
    }
    @RangeValidate(-50, 50)
    set temp(value: number) {
        this._temp = value
    }
    get temp() {
        return this._temp
    }
}

const w1 = new Weather(28);
w1.temp = 40;
console.log(w1.temp)