首页 前端知识 TypeScript 基础语法

TypeScript 基础语法

2024-05-31 19:05:22 前端知识 前端哥 711 238 我要收藏

(一) 类型

1、基元类型

类型例子描述
number1, -33, 2.5任意数字
string‘hi’, “hi”, `hi`任意字符串
booleantrue、false布尔值true或false
null值只能为null
undefined值只能为undefined
字面量其本身(详见 面向对象 15泛型)限制变量的值就是该字面量的值
any*任意类型
unknown*类型安全的any,unknown类型的变量不能直接赋值给其他变量。只能赋值给 any 类型和unknown类型。
void空值(undefined)没有值(或undefined)
never没有值不能是任何值
object{name:‘孙悟空’}任意的JS对象
array[1,2,3]任意JS数组
tuple[4,5]元组,TS新增类型,固定长度数组
enumenum{A, B}枚举,TS中新增类型
  • string::typeScript 中的字符串类型;String:JavaScript 的字符包装类型。
//声明一个变量str同时指定它的类型为string
let str: string = '123'; 
//ts自动推断myName为string类型,所以myName不能赋值除string类型之外的类型
let myName = '123';
//声明变量如果不指定类型,则自动判断为类型为any, any类型可以赋值各种类型
let d; //等同于 let d:any;

2、数组

  • 一般来说同一个数组存放的数据类型需要是相同的
//定义数字数组变量, 不能给 arr, arr2 数组元素数字以外的类型
let arr: number[] = [1, 2, 3]; // 【推荐】
//等同于
let arr2: Array<number> = [4, 5, 6]; // 【不推荐】在(react jsx 中有冲突)

//字符串和数字的混合数组
let arr3: (string | number)[] = ['1', 2];

3、函数

参数后边的 (: string) 类型称为:参数类型注释
函数括号后边的 (:void) 称为:返回值类型注释,可以省略,默认为void

//参数name的类型为string, 返回值类型为void(可以省略)
function greet(name: string): void {
    console.log(`hello, ${name}!!`);
}
greet('张三');

//返回值类型注释省略时,ts根据返回值判断出应该是什么类型
function getNumber(){ //等同于 getNumber(): number
    return 2;
}
getNumber();

//定义一个函数变量,参数类型为number,返回值类型为number
let fn: (a: number, b: number)=>number
fn = function(n1, n2): number{
	return n1 + n2;
}

4、对象类型与可选属性

//对象参数的x为number类型,y为可选参数,如果要传y,则需为number类型
function position(ps: {x: number, y?: number}){
    console.log('x坐标为:' + ps.x);
    console.log('y坐标为:' + ps.y);
}
let b = {x: 2};
position(b);

//变量c必须有一个string类型的name, 
//[propName: string]: any 表示变量c可以有任意的其他属性,属性名为string类型,值为任意类型。propName可以随意写
let c: {name: string , [propName: string]: any};
c = {name: '猪八戒', age: 12} 
//object对象类型用于描述一个对象
const info: object = {
	name:'张三',
	age: 18
}

info['name'] = '李四' // 报错
console.log(info.age) // 报错,提示类型“object”上不存在属性“age”

5、联合类型

let id: number | string = '123';
//等同于 function printId(id: number | string): number | string{
function printId(id: number | string){
   return id;
}

console.log(printId2('123'));
console.log(printId2(456));

6、类型别名

//定义类型别名
type Point = {
    x: number,
    y: number
}
function printPoint(pt: Point) {
    console.log(`x坐标:${pt.x}, y坐标:${pt.y}`);
}
printPoint({ x: 15, y: 16 });

//定义类型别名
type ID = number | string;

function getStrId(id: ID): string {
    return id.toString()
}

console.log(getStrId(123));

type Animal = {
	name: string
}
//扩展Animal
type Bear = Animal & {
    honey: boolean
}

7、交叉类型

定义:将多个类型合并(多个类型属性和方法的并集)成的类型就是交叉类型。
与联合类型的区别

  1. 赋值区别:
    交叉类型是多个类型属性和方法的合并后的类型,属于多个类型的并集,必须是两个类型的全部属性和方法才能赋值给交叉类型变量。【可选属性和方法除外】
    联合类型的变量可以接收联合类型中任意一种数据类型全部属性和方法, 也可以是两个类型的全部属性和全部方法【可选属性和方法除外】
    2. 获取属性和方法区别:
    交叉类型变量可以获取两个类型的任意属性和任意方法,而联合类型的变量只能获取两个类型的共同属性和方法【交集属性和交集方法】

    type myType1 = {name: string, age: number};
    type myType2 = {address: string, phone: string};
    
    //交叉类型:同时满足myType1, myType2
    let person: myType1 & myType2 = {
      name: '张三',
      age: 12,
      address: '地址',
      phone: '电话'
    };
    
    //联合类型:至少满足myType1, myType2中的一个
    let person2: myType1 | myType2 = {
      name: '张三',
      age: 12,
      phone: '电话' // myType2 的属性
    };
    

8、构造函数类型

class CommercialBank {
  public name: string;
  public age: number;

  
  constructor(_name: string, _age: number) {
    this.name = _name;
    this.age = _age;
  }
}

//代表 CommercialBank 类的构造函数的类型
//new 关键字 不是创建的意思,只是说明 (...arg: any) => any 是一个构造函数,并且范围类型为 any
type CommercialBankType = new (...arg: any) => CommercialBank;

//通用的构造函数类型
type ConstructorType = new (...arg: any) => any;

9、接口

//定义接口
interface Animal{
	name: string
}

//向接口添加字段
interface Animal{
	eat: string  //与name合并
}

//扩展接口
interface Bear extends Animal{
	honey: boolean
}

10、明确变量的值

//将变量x的类型设置为hello, 值只能是 hello
let x: 'hello' = 'hello';

function printText(str: string, alignment: 'left' | 'right'){
    console.log(str, alignment);
}

printText(x, 'left')

11、元组

元组就是固定长度的数组

//定义一个长度为2的第一个数组元素为string类型,第二个为number类型
let h: [string, number];
//数组元素类型和定义是的类型顺序要一致
h = [ '123',123];

12、枚举

  • 枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
  • 枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型;
enum Direction {
  LEFT = 1, //初始化一个值,如果不明确指定,从0开始
  RIGHT, //习惯上值大写
  TOP = "top",
  BOTTOM = 101
}

function turnDirection(direction: Direction) {
  switch (direction) {
    case Direction.LEFT:
      console.log('左转')
      break;
    case Direction.RIGHT:
      console.log('右转')
      break;
    case 'top':
      console.log('上转')
      break;
    case 101:
      console.log('下转')
      break;
    default:
      console.log('其他方向:' + direction)
  }
}

console.log(Direction.RIGHT);//输出2,在LEFT的基础上自增1
turnDirection(Direction.LEFT)
turnDirection(Direction.TOP)
turnDirection(Direction.BOTTOM)
turnDirection(123)

13、ts4 const 常量被修改

const arr1 = [1, 2];
arr1.push(3); //常量可以被修改
console.log(arr1);


const arr2 = [1, 2] as const;
// arr2.push(3); //报错
console.log(arr2);

function readArr(arr: any[]){
  arr.forEach(function(item){
    console.log(item)
  });
}

//添加readonly 表示参数只会被读
function readArr2(arr: readonly any[]){
  arr.forEach(function(item){
    console.log(item)
  });

  // arr.push(3); //报错
}

readArr(arr1);
// readArr(arr2); //报错arr2只能读,传递到readArr最后可能会被写
readArr2(arr2);

14、ts4 可变元组、元组标签

//元组 (长度固定)
let [userName, age]: [string, number] = ['张三', 12];
let info: [string, number] = ['李四', 23];
console.log(userName); //张三
console.log(age);//12
console.log(info); //['李四', 23]
console.log('-------------------------------------------');

//可变元组 , 不限制长度
let [dogName, bark, ...reset]: [string, string, ...any[]] = ['小黑', '汪汪', 12, 'lalala'];
let msg: [string, number, ...any] = ['消息', 23, '卡卡卡卡', 456];
console.log(dogName);//小黑
console.log(bark);//汪汪
console.log(reset); //[ 12, 'lalala' ]
console.log(msg);//[ '消息', 23, '卡卡卡卡', 456 ]
console.log('-------------------------------------------');

//元组标签
let [color, border, ...other]: [_color:string, _border:string, ..._other:any[]] = ['白色', '1px', 12, 'dodododood'];
console.log(color);//白色
console.log(border);//1px
console.log(other); //[ 12, 'dodododood' ]

(二) 函数

1、函数类型表达式

//接收一个函数参数fn,函数参数fn需要一个string参数,函数参数fn返回void
function print(fn: (str: string) => void){
    fn('hello world');
}

function printToConsole(str: string){
    console.log(str);
}

print(printToConsole);

2、函数重载

定义:

规则1:由一个实现签名+ 一个或多个重载签名合成。
规则2: 但外部调用函数重载定义的函数时,只能调用重载签名,不能调用实现签名,这看似矛盾的规则,其实 是TS 的规定:实现签名下的函数体是给重载签名编写的,实现签名只是在定义时起到了统领所有重载签名的作用,在执行调用时就看不到实现签名了。
规则3:调用重载函数时,会根据传递的参数来判断你调用的是哪一个函数
规则4: 只有实现签名配备了函数体;所有的重载签名都只有签名,没有配备函数体。
规则5: 关于参数类型规则完整总结如下:
实现签名参数个数可以少于重载签名的参数个数,但实现签名如果准备包含重载签名的某个位置的参数 ,那实现签名就必须兼容所有重载签名该位置的参数类型【联合类型或 any 或 unknown 类型的一种】。
规则6: 关于重载签名和实现签名的返回值类型规则完整总结如下:
必须给重载签名提供返回值类型,TS 无法默认推导。
提供给重载签名的返回值类型不一定是其执行时的真实返回值类型,可以为重载签名提供真实返回值类型,也可以提供 void 或 unknown 或 any 类型,如果重载签名的返回值类型是 void 或 unknown 或 any 类型,那么将由实现签名来决定重载签名执行时的真实返回值类型。 当然为了调用时能有自动提示+可读性更好+避免可能出现了类型强制转换,强烈建议为重载签名提供真实返回值类型。

优点:

优势1: 结构分明
让代码可读性,可维护性提升许多,而且代码更漂亮。
优势2: 各司其职,自动提示方法和属性:
每个重载签名函数完成各自功能,输出取值时不用强制转换就能出现自动提示,从而提高开发效率
优势3:更利于功能扩展

//定义类型 MessageType 
type MessageType = 'image' | 'audio' | 'other';
//定义类型 Message 
type Message = {
    id: number,
    type: MessageType,
    info: string
}

let messages: Message[] = [
    {id: 1, type: 'image', info:'这是第一条信息'},
    {id: 2, type: 'audio', info:'这是一条语音'},
    {id: 3, type: 'image', info:'信息3333'},
    {id: 4, type: 'image', info:'信息44444'},
];

//重载签名1:接受一个number类型的key返回 一个Message类型的数据
function getMessage(key: number): Message;
//重载签名2:接受一个MessageType类型的key, msgCount 表示返回结果的数量,返回一个Message类型的数组
function getMessage(key: MessageType, msgCount: number): Message[];

//实现签名(重载签名的方法具体实现)
//实现签名的参数 key 类型必须同时包含所有重载签名参数类型 any 也可以用 number | MessageType 代替
//msgCount默认取值为1,如果不给默认值,则 重载签名1 因为没有第二个参数,所以提示错误  
//msgCount 也可以使用  msgCount?: number 表示该参数是可选参数
//返回值必须同时包含所有重载签名的返回值
function getMessage(key: any, msgCount: number = 1): Message | Message[] | undefined{
    if(typeof key === 'number'){
        return messages.find(item => item.id === key);
    }else {
       return messages.filter(item => item.type === key).splice(0, msgCount);
    }
}

console.log(getMessage(3));//调用重载函数:实际调用的是getMessage(key:number): Message这个重载签名
console.log(getMessage(4)?.info); //数据不存在时返回undefined, 可以使用es6语法 .? 来规避报错
console.log(getMessage(3)?.info); //因为参数为number类型,所以返回值可以明确为Message类型,这时可以直接使用 .info 调用

console.log(getMessage('image', 2));//调用重载函数:实际调用的是function getMessage(key:MessageType): Message[];这个重载签名

//因为参数为number类型,所以返回值可以明确为Message类型的数组
//参数不存在时,返回空数组
getMessage('other', 1).forEach(function(item){
    console.log(item.info);
}) 


export {}; //当同一个文件夹下有多个同名class时可以使用该语句避免报错

(三) 面向对象

1、类(class)

class 类名{
	属性名: 类型;
	属性名: 类型 = 属性值;  //有属性值时,类型可以省略
	//静态属性
	static 属性名: 类型 = 属性值;
	//只读属性
	readonly 属性名: 类型 = 属性值;
	//静态只读属性
	static readonly 属性名: 类型 = 属性值;
	
	//构造函数
	constructor(参数: 类型){
		this.属性名 = 参数;
	}
	
	方法名(){
		//....
	}

	//静态方法
	static 方法名(){
		//....
	}
}
//Dog是一个类构造函数对象变量
//Dog也是创建类对象的一个类型
class Dog {
    name: string;
    age: number;
    readonly barkNum = 3;
    static Voice = '汪';

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    bark() {
        for (let i = 0; i < this.barkNum; i++) {
            console.log(Dog.Voice);
        }
    }
}

let dog = new Dog('大黄', 13);

dog.bark();

2、属性

class Order{
	//price存在可能不赋值的情况,为了避免报错加上感叹号
	//如果要给price赋值则必须是number数据类型
	public price!:number;
}

3、访问性修饰符

TS中属性具有三种修饰符:

  • public(默认值),可以在 属性定义的类中、子类和对象中修改
  • protected ,可以在 属性定义的类中、子类中修改
  • private ,可以在 属性定义的类中修改
class Person {
    public name: string; //public 可省略
    protected age: number;
    private think: number = Math.random(); //只能在定义的类中修改

    public constructor(name: string, age: number) {
        this.name = name; // 可以在类中修改
        this.age = age;
    }

    sayHello() {
        console.log(`大家好,我是${this.name}`);
    }

    printThink() {
        console.log(`我想的数字是:${this.think}`);
    }

    //封装私有属性
    getThink() { 
        return this.think;
    }

    setThink(think: number) {
        this.think = think;
    }
}

class Employee extends Person {
    constructor(name: string, age: number) {
        super(name, age);
        this.name = name;
    }
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒';
console.log(p.getThink());//通过封装的方法获取私有属性
p.setThink(10);//通过封装的方法设置私有属性
p.printThink();

4、构造函数(constructor)

当 new 出来一个对象时,构造器会隐式返回 this 给 new 对象等号左边的对象变量,this 和等号左边的对象变量都指向当前正创建的对象。

class Order {
    public orderId: number;
    public orderDate: Date;
    public userAddress: string;

	//构造函数相当于function Order (id_: number, date_: Date, address_: string) : Order 函数
    constructor(id_: number, date_: Date, address_: string) {
        this.orderId = id_;
        this.orderDate = date_;
        this.userAddress = address_;
        //return this; 隐式的将this返回
    }
}
//这两个类等同
class Order {
	//构造器简洁赋值:给构造函数的参数加上public,这个参数就变成了一个属性
    constructor(public orderId: number, public orderDate: Date, public userAddress: string) {

    }
}

5、构造器重载

构造器重载和函数重载使基本相同,主要区别是:TS 类构造器重载签名和实现签名都不需要管理返回值,TS 构造器是在对象创建出来之后,但是还没有赋值给对象变量之前被执行,一般用来给对象属性赋值。

具体实现规则看 函数重载

type sideObj = {
  width: number,
  height: number
}

//该类通过传递宽高参数或者宽高参数对象来计算面积
class Area {
  public width: number;
  public height: number;

  constructor(width_: number, height_: number);//重载签名1
  constructor(sideObj_: sideObj);//重载签名2

  constructor(sideObjOrWidth: number | sideObj, height_: number = 0) { //实现签名
    if (typeof sideObjOrWidth === 'number') {
      this.width = sideObjOrWidth;
      this.height = height_;
    } else {
      this.width = sideObjOrWidth.width;
      this.height = sideObjOrWidth.height;
    }
  }

  getArea() {
    return this.height * this.width;
  }
}

let area = new Area(12,12);
console.log(area.getArea());

let side: sideObj = {width: 13, height: 13};
let area2 = new Area(side);
console.log(area2.getArea());

6、静态属性和方法

  • 调用静态成员:类名直接调用静态成员 类名.静态属性,类名.静态方法
  • 静态方法只能调用静态方法和静态属性。
  • 静态属性一旦改变,其他静态方法或类外部任何地方访问这个属性都会发生改变。
  • 任何一个对象创建之前 TS 就已经为静态成员分配好了空间。但一个静态方法或静态属性只会分配一个空间。
class MyLocalstoryage {
  static total: number = 0;//静态的基本类型属性
  static mylocalstoryage: MyLocalstoryage; //静态的引用属性

  remark:string;

  public constructor(_remark: string){
    this.remark = _remark;
  }

  public static initMylocalstoryage(_remark: string) {
    if (!this.mylocalstoryage) {  //如果MyLocalstoryage 已经被实例化,则不再实例化(单例模式)
      console.log('实例被创建'); 
      this.mylocalstoryage = new MyLocalstoryage(_remark);//引用类型的静态属性可以赋值
    }

    return this.mylocalstoryage;
  }

  public static addTotal() { //静态方法只可以获取静态属性和静态方法
    this.total += 1;
  }

  public static getTotal() {
    return this.total;
  }

  getRemake(){
    console.log(this.remark)
  }
}

let mylocalstoryage = MyLocalstoryage.initMylocalstoryage('标记1');
let mylocalstoryage2 = MyLocalstoryage.initMylocalstoryage('标记2'); // 实际只创建了一个实例
MyLocalstoryage.addTotal()
console.log(MyLocalstoryage.getTotal()); //即使是在别的文件中也获得1
mylocalstoryage2.getRemake(); //显示结果:“标记1”, 表示只创建了一个实例

7、继承 (extends)

  • 继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写

    class Animal {
        name: string;
        age: number;
    
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
    
        bark(voice: string) {
            console.log(voice);
        }
    
        move(action: string) {
            console.log(action);
    
        }
    }
    
    class Cat extends Animal{
        //子类重写父类的方法
        move(action: string) {
            console.log(action);
        }
    }
    
    let cat = new Cat('小花猫', 12);
    
    cat.bark('喵喵喵');
    cat.move('轻轻地');
    

8、super 关键字

在子类中可以使用super来完成对父类的引用

class Bird{
    name: string;

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

    fly(){
        console.log(this.name + '在飞');
    }
}

class Sparrow extends Bird{
    age: number;

    constructor(name: string, age:number){
        super(name); //必须在首位
        this.age = age;
    }

    fly() {
        console.log('我的名字是:' + this.name);
        super.fly(); //调用父类的方法
    }
    
}

let sparrow = new Sparrow('麻雀', 13);
sparrow.fly();

9、只读属性

  • 类中的只读属性,可以在构造器中赋值,赋值之后就不可以修改
  • 对于只读的对象类型,对象类型中的属性是可以修改的
class Person {
  readonly name: string
  readonly friend?: Person
  age?: number

  constructor(_name: string, _age?: number, _friend?: Person) {
    this.name = _name
    this.age = _age
    this.friend = _friend
  }
}

const p1 = new Person('张三', 14)
const p2 = new Person('李四', 12, p1)


if (p2.friend) {
  p2.friend.age = 45
}

console.log(p1.age) // 45
console.log(p2.friend?.age)

export { }

10、getter 和 setter 访问器

class Person {
  private _name: string

  constructor(name: string) {
    this._name = name
  }

  //setter 访问器
  set name(newName) {
    this._name = newName
  }

  //setter 访问器
  get name() {
    return this._name
  }
}

const p = new Person('张三')
p.name = '李四'
console.log(p.name)  // 李四

export {}

11、类型断言 as

(1)TS 类型断言定义: 把两种能有重叠关系的数据类型进行相互转换的一种 TS 语法,把其中的一种数据类型转换成另外一种数据类型。类型断言和类型转换产生的效果一样,但语法格式不同。

(2)TS 类型断言语法格式: A 数据类型的变量 as B 数据类型 。A 数据类型和 B 数据类型必须具有重叠关系

(3) 重要细节:理解重叠关系: 以下几种场景都属于重叠关系

  1. 如果 A,B 如果是类并且有继承关系

    【 extends 关系】无论 A,B 谁是父类或子类, A 的对象变量可以断言成 B 类型,B 的对象变量可以断言成A类型 。但注意一般在绝大多数场景下都是把父类的对象变量断言成子类。

  2. 如果 A,B 是类,但没有继承关系

    两个类中的任意一个类的所有的 public 实例属性【不包括静态属性】加上所有的 public 实例方法和另一个类的所有 public 实例属性加上所有的 public 实例方法完全相同或是另外一个类的子集,则这两个类可以相互断言,否则这两个类就不能相互断言。

  3. 如果 A 是类,B 是接口,并且 A 类实现了 B 接口【implements】,则 A 的对象变量可以断言成 B 接口类型,同样 B 接口类型的对象变量也可以断言成A类型 。

  4. 如果 A 是类,B 是接口,并且 A 类没有实现了 B 接口,则断言关系和第2项的规则完全相同。

  5. 如果 A 是类,B 是 type 定义的数据类型【就是引用数据类型,例如 Array, 对象,不能是基本数据类型,例如 string,number,boolean】,并且有 A 类实现了 B type 定义的数据类型【 implements】,则 A 的对象变量可以断言成 B type 定义的对象数据类型,同样 B type 定义的对象数据类型的对象变量也可以断言成 A 类型 。

  6. 如果 A 是类,B 是 type 定义的数据类型,并且 A 类没有实现 B type定义的数据类型,则断言关系和第2项的规则完全相同。

  7. 如果 A 是一个函数上参数变量的联合类型,例如 string |number,那么在函数内部可以断言成 string 或number 类型。

  8. 多个类组成的联合类型如何断言? 例如:let vechile: Car | Bus | Trunck。 vechile 可以断言成其中任意一种数据类型。 例如 vechile as Car, vechile as Bus , vechile as Trunck 。

  9. 任何数据类型都可以转换成 any 或 unknown 类型 ,any 或 unknown 类型也可以转换成任何其他数据类型。

class Person {}
class Student extends Person {
	studying(){}
}

function sayHello(p: Person){
	(p as Student).stydying()   //从一个宽泛的类型断言为一个较窄的类型,并调用窄类型的方法
}

const stu = new Student()
sayHello(stu)

12、非空类型断言 !

非空断言表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测;

// 形参 message 可以不传,如果要传则需是 string 类型
function getMessageLength(message?: string){
	console.log(message!.length)  // 表示 message 一定有值
}

getMessageLength('hello')

13、类型守卫

类型守卫定义: 在 语句的块级作用域【if语句内或条目运算符表达式内】缩小变量的一种类型推断的行为。

类型守卫产生时机: TS 条件语句中遇到下列条件关键字时,会在语句的块级作用域内缩小变量的类型,这种类型推断的行为称作类型守卫 ( Type Guard )。类型守卫可以帮助我们在块级作用域中获得更为需要的精确变量类型,从而减少不必要的类型断言。

  • 类型判断:typeof (类型范围包括: “string" | “number” | “bigint” | “boolean” | “symbol” | “undefined” | “object” | “function” 等数据类型。) (Object.prototype.toString.call( 值 ))
  • 属性或者方法判断:in
  • 实例判断:instanceof
  • 字面量相等判断:==, ===, !=, !==

14、自定义类型守卫

返回布尔值的条件表达式赋予类型守卫的能力, 只有当函数返回 true 时,形参被确定为 A 类型

自定义守卫格式:

function  函数名( 形参:参数类型【参数类型大多为any)  : 形参 is A类型{
	return  true or false
}
function isString(str: any): str is string {
  return typeof str === 'string';
}

function isNumber(str: any): str is number {
  return typeof str === 'number';
}

function getValType(val:any){
  if(isString(val)){ //通过isString 方法, 可以确定 val 是 String 类型
    console.log(val.length); //输入 . 后可以直接显示出字符串的共有方法
  }else if(isNumber(val)){
    console.log(val.toFixed(2))
  }
}

getValType(123)

15、多态

1.多态的定义:
父类的对象变量可以接受任何一个子类的对象,从而用这个父类的对象变量来调用子类中重写的方法而输出不同的结果.
2.产生多态的条件:

  1. 必须存在继承关系
  2. 必须有方法重写

3.多态的局限性
无法直接调用子类独有方法,必须结合instanceof类型守卫来解决


class Parent {
  eat() {
    console.log('parent 的 eat 方法被调用')
  }
}

class Son extends Parent {
  eat() {
    console.log('Son 的 eat 方法被调用')
  }
}

class Daughter extends Parent {
  eat() {
    console.log('Daughter 的 eat 方法被调用')
  }

  dance(){
    console.log('Daughter 的 dance 方法被调用')
  }
}

//创建一个Parent类型的变量
var people: Parent = new Parent();
people.eat();

people = new Son();
people.eat();

people = new Daughter();
people.eat();

//无法调用子类中特有的方法
// people.dance();

//可以使用 instanceof 判断变量的类型
if(people instanceof Daughter){
  people.dance();
}

16、抽象类 与 抽象函数 (abstract)

  • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例

  • 抽象方法只能定义在抽象类中,子类必须对抽象方法重写

    abstract class Bird {  //只能被继承不能被实例化(new Bird() 错误)
      construct() {
        //也有自己的构造器
      }
      //...
      //定义一个抽象方法(抽象方法可以有 0 个 也可以有多个)
      abstract sayHello(): void;
      //也可以有具体的方法
      public fly() {
        console.log('bird的fly')
      }
    }
    
    class Sparrow extends Bird {
      //...  
      sayHello() {
        //子类必须重写父类的抽象方法
      }
    
      public fly(): void {
        super.fly()
      }
    }
    
    var sw = new Sparrow();
    
    sw.fly();
    

17、接口(interface)

  • 接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。
  • 接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。
  • 多个相同名称的接口会合并接口中属性
interface Person{
    name: string;
    sayHello():void;
}

class Student implements Person{
    constructor(public name: string) {
    }

    sayHello() {
        console.log('大家好,我是'+this.name);
    }
}

18、泛型(Generic)

1). 泛型
  • 泛型是一种参数化数据类型,具有以下特点的数据类型叫泛型:
    1.定义时不明确,使用时必须明确成某种具体数据类型的数据。
    2.编译期间进行数据类型安全检查的类型。
  • 泛型的好处:
    1.编译期间可以进行类型安全检查;
    2.根据泛型形参类型的不同,可以接收不同类型的变量;
    3.获取属性和方法时有自动提示;
  • 泛型格式:
    //泛型形参类型 必须是A-Z中的字母或者语义化的单词,一般是字母
    //class 类名<泛型形参类型> { ... }
    //class MyClass<T=any> {...}  =>  可以给T一个默认类型any
    class MyClass<T> {
      public arr!: Array<T>; //创一个T类型的数组
    
      public fn(ags: T): any { //方法的参数arg是T类型的
        return ags;
      }
    }
    
    //T 为 unknwon 类型
    //var myClass = new MyClass();
    
    //明确指定T为string类型
    var myClass = new MyClass<string>();
    myClass.fn(123); //进行安全检查,会提示参数只能是string类型
    
2). 泛型约束

T extends object: 表示 T 具体化的泛型类型只能是 object (在此例子中为object) 类型,某个变量如果能断言成 object 类型 (变量 as object),那么这个 变量就符合 T extends object。就是说该变量类型可以是 T 的具体类型。

type arrType1 = Array<string>;
type arrType2 = Array<number>;

//T 只能是 string 或 number 类型的数组
class ArrayControl <T extends (arrType1 | arrType2)>{
  private myArr: T;

  constructor(_t: T){
    this.myArr = _t;
  }
 
  showAllItem(){
    this.myArr.forEach((item: any) => {
        console.log(item);
    });
  }
}

let arr1 = new ArrayControl([1,2]);
arr1.showAllItem();
let arr2 = new ArrayControl(['1', '2']);
arr2.showAllItem();

// let arr3 = new ArrayControl([1, '2']);//报错
// let arr4 = new ArrayControl([{name: '张三', age: 24}]);//报错  
3). keyof

keyof 表示获取一个类或者一个对象类型或者一个借口类型的所有属性名(key)组成的联合类型。

/******************* 例子1 *********************************/
//具体的值可以当类型, num1 的类型就是 1, 值也只能是 1
type num1 = 1;

let myNumber: num1 = 1;
// let myNumber2 : num1 = 2; //报错:无法将类型2的值分配给类型1

/******************* 例子2 *********************************/
//定义一个变量 obj
let obj = { name: '张三', age: 12 };
//定义一个 myObj 类型
type myObj = keyof typeof obj;
//相当于myObj2, 所以 myObj , myObj2 的类型为 name和age 的联合类型,值也只能从 name和age 这两个中选
type myObj2 = "name" | "age";

let a: myObj = 'name';
let b: myObj2 = 'age';
// let c: myObj = 'age123';//报错


/******************* 例子3 *********************************/
class MyClass {
  public address: string;
  private eMail: string;
  public static phone = '123';

  constructor(_address: string, _eMail: string) {
    this.address = _address;
    this.eMail = _eMail;
  }

  getAddress() {
    let user: string = '张三';
  }
}

//定义一个 classType 类型, classType是 address 和 getAddress 组成的联合类型
type classType = keyof MyClass;

let myclass: classType = 'address';
let myclass2: classType = 'getAddress';
// let myclass3: classType = 'eMail';
// let myclass4: classType = 'phone';

//MyClass['address'] 获得 address 的类型
type myClassType = MyClass['address'];
//相当于
type myClassType2 = string;

export { }
4). 泛型 和 keyof 组合
//T的取值为 address 和 getAddress
class ObjectImpl<T extends object, K extends keyof T>{
  public myObject: T;
  public key: K;

  constructor(_myObject: T, _key: K) {
    this.myObject = _myObject;
    this.key = _key;
  }

  getValue() {
    return this.myObject[this.key];
  }

  //当K取值 'address' 的 T[k] 值为string, 则newValue 类型为 string
  setValue(newValue: T[K]) {
    this.myObject[this.key] = newValue;
  }
}


class MyClass {
  public address: string;

  constructor(_address: string) {
    this.address = _address;
  }

  getAddress() {
    let user: string = '张三';
  }
}

let myClass = new MyClass('非洲');

let oi = new ObjectImpl(myClass, 'address');
console.log(oi.getValue());
oi.setValue('亚洲')
console.log(oi.getValue());
5). 泛型接口

好处:

  1. 降低代码管理成本,提供统一属性和方法命名。

  2. 可以从整体上快速通读类的共同方法和属性。

  3. 和多态结合增加了项目的扩展性和简洁成都,对开发大中型项目有好处。

    interface List<T> {
      add(newValue: T): void;
      getItemByIndex(index: number): T;
      getListSize(): number;
      removeItemByIndex(index: number): T;
    }
    
    
    //实现接口, 点击MyList自动填充 接口中的方法
    class MyList<T> implements List<T>{
      add(newValue: T): void {
        throw new Error("Method not implemented.");
      }
      getItemByIndex(index: number): T {
        throw new Error("Method not implemented.");
      }
      getListSize(): number {
        throw new Error("Method not implemented.");
      }
      removeItemByIndex(index: number): T {
        throw new Error("Method not implemented.");
      }
    }
    
6). 泛型函数

格式: 函数名 <泛型类型1, 泛型类型2> (参数,可以使用泛型类型):返回值也可以泛型类型

function quickSort<T>(arr: Array<T>): Array<T> {
  return arr.sort((firstItem, secondItem) => {
    return (firstItem as any) - (secondItem as any);
  });
}

let address = [5,6,3,6,8];

console.log(quickSort(address));
7). 泛型函数与工厂方法
class CommercialBank {
  public bankName: string;

  constructor(_bankName: string) {
    this.bankName = _bankName;
  }

  getBankName(){
    console.log(this.bankName);
  }
}


//new (...args: any) => CommercialBank 代表的是 CommercialBank 的构造函数 constructor
//将鼠标放置在 CommercialBank 的构造函数上 可以发现与 new (...args: any) => CommercialBank 一致
//typeof CommercialBank 等于 new (...args: any) => CommercialBank
//type CommercialBankType = new (...args: any) => CommercialBank;


//通用的构造函数类型
// type ConstructorType = new (...args: any) => any;

//1.工厂函数
// { new(...args: any): any }  构造函数返回的是 any 类型, 所以整个函数返回的是any类型
// function createInstannceFactory(Constructor: new (...args: any) => any) 等同于
function createInstannceFactory(Constructor: { new(...args: any): any }) {
  return new Constructor('光大银行');
}

let cb1 = createInstannceFactory(CommercialBank);
cb1.getBankName();

//2. 泛型工厂函数,{ new(...args: any): T } 构造函数返回的是 T 类型,所以整个函数返回的是 T 类型
function createInstannceFactory2 <T>(Constructor: { new(...args: any): T }) {
  return new Constructor('光大银行');
}

let cb2 = createInstannceFactory2 <CommercialBank>(CommercialBank);
cb2.getBankName();

//2. 泛型工厂函数
type factoryType<T> =  { new(...args: any): T } ;
function createInstannceFactory3 <T>(Constructor: factoryType<T>) {
  return new Constructor('光大银行');
}


let cb3 = createInstannceFactory3<CommercialBank>(CommercialBank);

cb3.getBankName();
export {}
8). 带参数的工厂方法
class ChinesePeople {
  public name: string;
  public sex: string;
  public age: number;

  constructor(_name: string, _sex: string, _age: number){
    this.name = _name;
    this.sex = _sex;
    this.age = _age;
  }

  getAge(){
    return this.age;
  }
}

//创建变量 UserChinesePeople 类型为 new(name: string, sex:string, age: number)=>ChinesePeople
//new(name: string, sex:string, age: number)=>ChinesePeople 就是 ChinesePeople 类的构造函数 (具体可以查看 泛型函数与工厂方法)
//变量值为 ChinesePeople 类
let UserChinesePeople : new(name: string, sex:string, age: number)=>ChinesePeople = ChinesePeople;

//UserChinesePeople 等同于 UserChinesePeople2
let UserChinesePeople2 : typeof ChinesePeople = ChinesePeople;

//创建 UserChinesePeople 实例,也即 ChinesePeople 实例
let cp = new UserChinesePeople('张三', '男', 23);
console.log(cp.getAge());


//泛型 与带参数的工厂方法
type SelfConstructor<T> = new (...args: any[]) => T;
let UserChinesePeople3 : SelfConstructor<ChinesePeople> = ChinesePeople;
let cp2 = new UserChinesePeople('李四', '女', 25);
console.log(cp2.getAge());

19、推断 (infer)

  • 定义:infer 表示在 extends 条件语句中以占位符出现的用来修饰数据类型的关键字,被修饰的数据类型等到使用时才能被推断出来。

  • infer占位符式的关键字出现的位置:
    1.infer 出现在 extends 条件语句后的函数类型的参数类型位置上;
    2.infer 出现在 extends 条件语句后的函数类型的返回值类型上;
    3.inger 出现在类型的泛型具体化类型上。

  • 注意事项:只能用在条件类型的 extends 子句中,并且 infer 声明的类型变量只在条件分支的 true 分支中使用

    interface Customer {
      custName: string,
      buyMoney: number
    }
    
    type custFuncType = (cust: Customer) => string;
    type custFuncType2 = (cust: Customer, str: string) => string;
    
    //1. infer 出现在 extends 条件语句后的函数类型的参数类型位置上;
    //(params: infer P) => any 表示的就是 类型 (params: infer P) => any,而非函数
    //T extends (params: infer P) => any 表示 T 是否 继承自 (params: infer P) => any
    // infer P 表示待推断的 类型 P
    //T extends (params: infer P) => any ? P : T 的意思就是,如果 T 能赋值给 (params: infer P) => any 
    //则返回的是 (params: infer P) => any 中的类型 P 否则返回  T
    type inferType<T> = T extends (params: infer P) => any ? P : T;
    //2. infer 出现在 extends 条件语句后的函数类型的返回值类型上;
    type inferType2<T> = T extends (params: any) => infer P ? P : T;
    
    //type inferResultType = Customer
    //T 为 custFuncType, P 为 Customer, any ? P : T 返回 P
    type inferResultType = inferType<custFuncType>;
    
    //inferResultType2 = (cust: Customer, str: string) => string;
    //T 为 custFuncType2,P为 Customer, any ? P : T 返回 T
    type inferResultType2 = inferType<custFuncType2>;
    
    // inferResultType3 = string;
    type inferResultType3 = inferType2<custFuncType>;
    
    class Subject {
      constructor(subId: number, subName: string) {
    
      }
    }
    
    let math = new Subject(1, '数学');
    let chinese = new Subject(2, '语文');
    let english = new Subject(3, '英语');
    
    let setSubject = new Set<Subject>([math, chinese, english]);
    
    //3.inger 出现在类型的泛型具体化类型上。
    type ElementOf<T> = T extends Set<infer E> ? E : never;
    
    type ss = typeof setSubject;
    
    // result: Subject
    let result: ElementOf<ss>;
    

20、字面量赋值 (擦除操作 freshness)

interface IPerson {
  name: string,
  age: number,
  height: number
}

//报错, 这里ts自动推导值的类型为 {name:string, age: number, height: number, address: string}
// 不符合 IPerson 的要求
// 原因:TypeScript在字面量直接赋值的过程中,为了进行类型推导会进行严格的类型限制

// const p: IPerson = {
//   name: '张三',
//   age: 18,
//   height: 1.88,
//   address: '上海'
// }

//-----------------------------------------------
const info = {
  name: '张三',
  age: 18,
  height: 1.88,
  address: '上海'
}
//不报错
//原因:将一个 变量标识符 赋值给其他的变量时,会进行freshness擦除操作
const p: IPerson = info  
console.log(p); //{ name: '张三', age: 18, height: 1.88, address: '上海' }

export {}

(四) 高级类型

TS中常用的工具映射类型,让写TS时效率大大提升,避免无意义的重复性定义。

1、包括 (Extract)

// Extract实现源码 原理很简单
//在TS中已经存在无需自己重复定义
type Extract<T, U> = T extends U ? T : never;
  1. 父子类

    class People {
      public name: string;
      public age: number;
    
      constructor(_name: string, _age: number) {
        this.name = _name;
        this.age = _age;
      }
    }
    
    class ChinesePeople extends People {
    
    }
    
    class ChinesePeople2 extends People {
      public sex: string;
    
      constructor(_name: string, _age: number, _sex: string) {
        super(_name, _age);
        this.sex = _sex;
      }
    }
    
    class ChinesePeople3 extends People {
      private address!: string;
    }
    
    
    //ChinesePeople
    type extractType = Extract<ChinesePeople, People>;
    //ChinesePeople2
    type extractType2 = Extract<ChinesePeople2, People>;
    //ChinesePeople3
    type extractType3 = Extract<ChinesePeople3, People>;
    //注意这里不使用ChinesePeople,而是使用的 ChinesePeople2, 因为 ChinesePeople 和 People 完全相同
    //never
    type extractType4 = Extract<People, ChinesePeople2>;
    
  2. 联合类型

    //string
    type unionExtractType1 = Extract<string, string | number>;
    //string | number
    type unionExtractType2 = Extract<string | number, string | number>;
    //第一步:Extract<string , string>; 返回 string
    //第二步:Extract<number , string>; 返回 never
    //string 
    type unionExtractType3 = Extract<string | number, string>;
    
    //never
    type unionExtractType4 = Extract<symbol | number, string>;
    
  3. 函数

    type fun1 = (one: number, two: string) => string;
    type fun2 = (one: number) => string;
    type fun3 = (one: number, two: string) => symbol;
    type fun4 = (one: symbol, two: string) => string;
    
    //1. 参数数量不同时, 多参数 => 少参数, never
    type fnType1 = Extract<fun1, fun2>;
    
    //2. 参数数量不同时, 少参数 => 多参数, (one: number) => string
    type fnType2 = Extract<fun2, fun1>;
    
    //3. 函数返回类型不同时,never
    type fnType3 = Extract<fun3, fun1>;
    
    //4. 参数类型不同时,never
    type fnType4 = Extract<fun4, fun1>;
    

2、不包括 (Exclude)

和 Extract 正好相反,排除条件成立的类型,保留不符合泛型约束条件的类型。

// Exclude源码
type Exclude<T, U> = T extends U ? never : T;

用例:

//string
type result1 = Exclude<string | number, number>;
//'name' | 'age'
type result2 = Exclude<'name' | 'age' | 'sex', 'sex'>;
//'name' | 'age' | 'sex'
type result3 = Exclude<'name' | 'age' | 'sex', 'address'>;


interface Worker1 {
  name: string;
  age: number;
  email: string;
}

interface Student {
  name: string;
  age: number;
  id: string;
  play: string;
}

//"email"
type result4 = Exclude<"name" | "age" | "email", keyof Student>;

// "email"
type result5 = Exclude<keyof Worker1, "name" | "age" | "id" | "play">;

//获取Worker接口中存在,但是Student中不存在的属性
//"email"
type result6 = Exclude<keyof Worker1, keyof Student>;

3、Record 准备:extends keyof

1). K extends keyof T
type Customer = {
  custname: string,
  age: number
}

// one = custname | age;
type one = keyof Customer;

type oneType<T, K> = T extends keyof K ? T : never;

//oneTypeResult = custname
type oneTypeResult = oneType<'custname', Customer>;

export { };
2). K extends keyof any
//anyType = string | number | symbol;
type anyType = keyof any;

type oneType<K> = K extends keyof any ? K : never;

//result1 = string;
type result1 = oneType<string>;

//result1 = 2; => 2 extends number 为 true 返回 2
type result2 = oneType<2>;

export{}

4、Record

// Record实现源码
//在TS中已经存在无需自己重复定义
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
1). 基础
type Customer = {
  userName: string,
  age: number
}

// ------- 1 ---------------
//K 必须是 number string symbol 类型中的一种
//P 要遍历 in 后边的
type Record<K extends keyof any, T> = {
  [P in 'key1' | 'key2']: T
}

// result1 = {
//   userName: Customer,
//   age: Customer
// }
type typeResult = Record<string, Customer>;
let result1: typeResult = {
  key1: {
    userName: '张三',
    age: 12
  },
  key2: {
    userName: '李四',
    age: 13
  }
}

// ------- 2 ---------------
type Record2<K extends keyof any, T> = {
  [P in string]: T
}

// Record2 = {
//   [x: string]: Customer, // x 只要是string类型即可
// }
type typeResult2 = Record2<string, Customer>;

let result2: typeResult2 = {
  'key1': {
    userName: '王五',
    age: 14
  }
}


// ------- 3 ---------------
type Record3<K extends keyof any, T> = {
  [P in K]: T
}

// Record3 = {
//   [x: number]: Customer, // x 只要是string类型即可
// }
//Record3 的 K 泛型只要是 number string symbol 类型中的一种即可
type typeResult3 = Record3<number, Customer>;

let result3: typeResult3 = [{
  userName: '王五',
  age: 14
}]

let result4: typeResult3 = {
  1: {
    userName: '王五',
    age: 14
  }
}


type typeResult4 = Record3<string, Customer>;

let result5: typeResult4 = {
  'key': {
    userName: '王五',
    age: 14
  }
}
export { }; 
2). 利用 K in T / Record实现数据扁平化
const goodSymId = Symbol('goodId');

interface Goods {
  [goodSymId]: number, //goodSymId 为常量所以需要用 [] 扩住, [] 中也可以用变量
  goodName: string;
  price: number
}

let goodsList: Goods[] = [{
  [goodSymId]: 101,
  goodName: '苹果',
  price: 65
},
{
  [goodSymId]: 102,
  goodName: '香蕉',
  price: 65
},
{
  [goodSymId]: 103,
  goodName: '梨',
  price: 65
}];

//--------------P in K -----
type Record<K extends keyof any, T> = {
  [P in K]: T
};

//type typeGood = {
//   [x: number]: Goods;
// }
type typeGood = Record<number, Goods>;
let goodRecord: typeGood = {};
goodsList.forEach(function(good){
  goodRecord[good[goodSymId]] = good;
});

// {
//   '101': { goodName: '苹果', price: 65, [Symbol(goodId)]: 101 },
//   '102': { goodName: '香蕉', price: 65, [Symbol(goodId)]: 102 },
//   '103': { goodName: '梨', price: 65, [Symbol(goodId)]: 103 }
// }
console.log(goodRecord);


//--------------P in keyof any -----
// [x:string] 既可以代表 [x: number] 也可以代表 [x: symbol], 所以 [P in keyof any] 就只有 [x:string]
//type Record2<T> = {
//   [x: string]: T;
// }
type Record2<T> = {
  [P in keyof any]: T
};

type typeGood2 = Record2<Goods>;
let goodRecord2: typeGood2 = {};
goodsList.forEach(function(good){
  goodRecord2[good[goodSymId]] = good;
});
console.log(goodRecord2);


export { };

5、采集 (Pick)

Pick 主要用于提取某种数据类型的属性,但实际工作中,主要用来提取接口或type定义的对象类型中的属性。

// Pick 实现源码
//在TS中已经存在无需自己重复定义
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
interface Book{
  ISBN: string,
  bookName: string,
  bookPrice: number,
  count: number,
  publish: string
}

//只需要Book接口中的个别属性
type BookPick = Pick<Book, 'ISBN' | 'bookName'>;

let newBook: BookPick = {
  ISBN: '123',
  bookName:'书名'
}

6、必选 (Required)

Required 把定义好的对象(包含 必选+可选项)类型全部转化为 必选项

// Required 实现源码
//在TS中已经存在无需自己重复定义
type Required<T> = {
    [P in keyof T]-?: T[P];
};
interface Todo {
  title: string,
  other?: string //可选属性
}

//将other属性 改为必选
// type TodoRequiredType = {
//   title: string;
//   other: string;
// }
type TodoRequiredType = Required<Todo>;

let todoItem: TodoRequiredType = {
  title: '标题',
  other: '其他'
}

7、只读 (ReadOnly )

Readonly 就是为类型对象每一项添加前缀 Readonly

// ReadOnly 实现源码
//在TS中已经存在无需自己重复定义
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
interface Person {
  readonly name: string; // 只有这一项有readonly
  age: number;
}

// type readonlyType = {
//   readonly name: string;
//   readonly age: number;
//   readonly id?: number | undefined;
// }
type readonlyType = Readonly<Person>

const newObj: readonlyType = {
  name: '张三',
  age: 1,
};
// newObj.age = 2; // 异常 因为有readonly只读属性,只能初始化声明,不能赋值。

8、Partial

可把定义好的对象(包含 必选+可选项)类型全部转化为可选项

// Partial 实现源码
//在TS中已经存在无需自己重复定义
type Partial<T> = {
    [P in keyof T]?: T[P];
};
interface Person {
  name: string;
  age: number;
  id: number;
}

// type partialType = {
//   name?: string | undefined;
//   age?: number | undefined;
//   id?: number | undefined;
// }
type partialType = Partial<Person>;


let newObj: partialType = {
  name: '张三' // 假如只需要一项 Partial的便捷性 可以不需要从新定义类型
};

export {}

9、省略/剔除 (Omit)

// Omit 实现源码
//在TS中已经存在无需自己重复定义
type Omit<T, K extends keyof T> = Pick<T, exclude<keyof T, K>>
interface Todo1 {
  title: string,
  completed: boolean,
  description: string
}

//type excludeType = "title" | "completed"
type ExcludeType = Exclude<keyof Todo1, 'description'>;
//type PickType = { title: string; completed: boolean; }
type PickType = Pick<Todo1, Exclude<keyof Todo1, 'description'>>


//等同于
//type PickType2 = { title: string; completed: boolean; }
type PickType2 = Omit<Todo1, 'description'>;

(五) 装饰器

  • 定义:装饰器就是一个函数,可以注入到类、方法、属性、参数、对象上,扩展其功能。
  • 作用:装饰器就是解决在不修改原来类、方法、属性、参数的情况下为其添加额外的功能。
  • 常见的装饰器分类:类装饰器、属性装饰器、方法装饰器、参数装饰器、元数据装饰器
  • 元数据装饰器:在定义类或类方法的时候,可以设置一些元数据,我们可以获取到在类与类方法上添加的元数据,需要引入 relect-metadata 第三方库,采用 @Reflect.metadata 来实现。元数据指的是描述东西时用的数据,例如:Reflect.metadata(‘importinfo’, ‘一段信息’)

1、环境搭建

  1. 安装 concurrently 支持合并多个执行命令,同时运行多个 script 命令:

    cnpm i concurrently -S
    
    cnpm i nodemon -S
    
  2. tsconfig.json 文件修改如下

    /*编译ts文件的输出目录*/
    "outDir": "./dist",       
     /*源ts文件*/
      "rootDir": "./src",
    /*消除装饰器警告*/
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true, 
    
  3. package.json 文件的 scripts 如下

    "scripts": {
    	//实时监控ts的变化,编译处理成js文件,
        "dev:build": "tsc -w",
        /*监控 dist/teaching 目录中的 js 文件, 变化时对1ClassDecorator.js 文件执行 node 命令*/
        "dev:start": "nodemon --watch dist/teaching js --exec node ./dist/teaching/1ClassDecorator.js",
        //合并执行 "dev:build" 和 "dev:start"
        "start": "concurrently npm:dev:*"
        /*解决 src/teaching/1ClassDecorator.ts文件编译装饰器类时出现的 bug*/
        /*--target ES5: 按照ES5语法编译*/
        /*-w:如果文件有变化,则重新编译*/
        /*--experimentalDecorators: 消除编译装饰器时的BUG*/
        "tsc": "tsc src/teaching/1ClassDecorator.ts --target ES5 -w --experimentalDecorators",
    
        /*后边章节会用到,先配置上*/
        "ctrl": "ts-node src/controller/HomeController.ts",
        "beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
      },
    
  4. 创建 src/teaching/1ClassDecorator.ts文件,并写一个装饰器

    //不带参数的装饰器
    function FirstClassDescrator(targetClass: any) {
      let targetCLassObj = new targetClass();
      targetCLassObj.buy();
      console.log('targetClass.name', targetClass.name);
    }
    
    @FirstClassDescrator
    class CustomerService {
      name: string = '张三'
      constructor() { }
      buy() {
        console.log(this.name + '购买')
      }
    }
    
    //带参数的装饰器
    function SecodeClassDescrator(params: any) {
      console.log("params:", params);
    
      return function (targetClass: any) {
        let targetCLassObj2 = new targetClass();
        targetCLassObj2.buy();
        console.log('targetClass.name', targetClass.name);
      }
    }
    
    @SecodeClassDescrator('我的装饰器的参数')
    class CustomerService2 {
      name: string = '李四'
      constructor() { }
      buy() {
        console.log(this.name + '购买')
      }
    }
    
  5. 在控制台执行编译命令,会在控制台输出内容

    npm run start
    
  6. 如果希望编译出来的js文件符合ES5规范,可以执行
    生成的 js 文件在 src/teaching 文件夹下

    npm run tsc
    

    生成的文件中的 __decorate 变量

    // 1. 底层JS 组合装饰器和目标类 __decorate函数
    var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
      // argsnum 参数个数
      var argsnum = arguments.length;
      // targetinfo 被装饰器修饰的目标【本案例为类】
      // argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]
      // argsnum=4 装饰器修饰的是方法【第四个参数desc等于null] targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】
      // argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefined
      var targetinfo = argsnum < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;//S100
      // decorator保存装饰器数组元素
      var decorator;
      // 元数据信息,支持reflect-metadata元数据
      if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
        targetinfo = Reflect.decorate(decorators, target, key, desc);
      } else
        //  装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行
        for (var i = decorators.length - 1; i >= 0; i--) {
          if (decorator = decorators[i]) {
            // 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类
            // 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo) 
            // 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)
            // targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfo
            targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
              decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
            console.log("targetinforesult:", targetinfo)
          }
        }
      return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
    }
    // 底层JS 组合装饰器和目标类 __decorate函数结束
    

2、工厂类装饰器

function LoggerInfoDecorator<T extends { new(...args: any): any }>
  (targetClass: new (...args: any) => Test) {

  class LoggerSonClass extends targetClass {
    constructor(...args: any) {
      super(...args)
      console.log("日志信息...targetClass:", (targetClass as any).name);
    }
    methodone() {
      console.log("methodone:", this.name);
    }
  }

  return LoggerSonClass
}
// 2. 目标类
// 类型 "typeof LoggerSonClass" 没有调用签名。
@LoggerInfoDecorator
class Test {
  name!: string;
  age!: number
  // 1.先执行原来构造函数
  constructor(name: string) {
    this.name = name;
  }
  eat() {
    console.log(this.name, "吃饭");
  }
}
export { }

3、方法装饰器

在方法装饰器中拦截目标类的方法,可以壮大或修改目标类的方法的功能。

// 1 不带参数的方法装饰器
/**
 * @param targetClassPrototype 方法所属的class
 * @param methodName 方法名
 * @param methodDecri 方法属性
 */
function MyMethodDecorator(targetClassPrototype: any, methodName: string, methodDecri: PropertyDescriptor) {
  console.log("targetClassPrototype:", targetClassPrototype)
  console.log("methodName:", methodName);
  console.log("methodDecri:", methodDecri);
}

class RoleService {
  public roleName: string = "管理员"
  constructor() {
  }

  @MyMethodDecorator
  DistribRoles() {// 分配角色
    console.log("分配角色.....");
  }
}



//2 带参数的方法装饰器
function MyMethodDecorator2(msg: string) {
  return function MyMethodDecorator(targetClassPrototype: any, methodName: string, methodDecri: PropertyDescriptor) {
    console.log("targetClassPrototype:", targetClassPrototype)//  
    console.log("methodName:", methodName);
    console.log("methodDecri:", methodDecri);
    console.log("msg:", msg);
  }
}


class RoleService2 {
  public roleName: string = "管理员2"
  constructor() {
  }

  @MyMethodDecorator2('我是参数2')
  DistribRoles2() {// 分配角色
    console.log("分配角色2.....");
  }
}
export { }

4、属性装饰器

function loginProperty(attrValue: any) {
  return function (targetclassPrototype: any, attrname: string | symbol) {
    console.log("targetclassPrototype:", targetclassPrototype);
    console.log("attrname:", attrname);
    targetclassPrototype.constructor.custLevelDescri = function () {
      console.log("消费5000元升级为贵宾");
      console.log("消费10000元升级为贵宾,赠送微波炉一个");
    }
    console.log("targetclassPrototype.custLevelDescri", targetclassPrototype.custLevelDescri);
  }
}

class CustomerService {

  public custname: string = "王五"

  @loginProperty("顾客登记")
  public degree!: string
  constructor() {
  }

  show() {
    console.log("顾客名:", this.custname)
  }
}

//(CustomerService as any).custLevelDescri()

export { }

(六) 其他

0、环境搭建

  • 根据这篇文章搭建ts环境,并安装Parcel插件,文章中的./src/base.js在下边示例中用src/main.ts代替

1、模块化

  • 模块化:每个文件可以是一个独立的模块,支持ES Module,也支持CommonJS;

  • 在src目录下新建 src/utils/math.ts

    // 定义函数 add
    export function add(num1: number, num2: number) {
      return num1 + num2
    }
    
  • src/main.ts

    // 导入 add 函数
    import { add } from './utils/math'
    
    console.log(add(1, 2))
    
  • 可以在浏览器的控制台中看到结果

2、命名空间 namespace

命名空间在TypeScript早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题。

  • 在src目录下新建 src/utils/format.ts

    	// 导出命名空间time
    	export namespace time {
    	  // time 中的作用域
    	  let currentDateTime = new Date()
    	  export function format(time: string) {
    	    return '2022-08-09'
    	  }
    	}
    	
    	export namespace price {
    	  // price 中的作用域
    	  export function format(price: number) {
    	    return '0.00'
    	  }
    	}
    
  • src/main.ts

    //----------------------------
    import { time, price } from './utils/format'
    
    console.log(time.format('123'))
    console.log(price.format(12))
    

3、类型声明 (declare)

  • 我们之前编写的typescript文件都是 .ts 文件,这些文件最终会输出 .js 文件,也是我们通常编写代码的地方;
  • 还有另外一种文件 .d.ts 文件,它是用来做类型的声明(declare)。 它仅仅用来做类型检测,告知typescript我们有哪些类型;
  • 那么typescript会在哪里查找我们的类型声明呢
    • 内置类型声明;
    • 外部定义类型声明;
    • 自己定义类型声明;
1). 内置类型声明
  • 内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件;
  • 包括比如Math、Date等内置类型,也包括DOM API,比如Window、Document等;
  • 内置类型声明通常在我们安装typescript的环境中会带有的,可以在github 查看内置的类型声明
2).外部定义类型声明
  • 外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明。
    这些库通常有两种类型声明方式:
    • 方式一:第三方库中已经写了类型声明(编写.d.ts文件),比如axios
    • 方式二:第三方库和自己的类型声明文件不在一起,需要自己下载
      • 第三方库声明文件的存放地址:https://github.com/DefinitelyTyped/DefinitelyTyped/
      • 可以通过查询查找对应的声明文件:https://www.typescriptlang.org/dt/search?search=
      • 比如我们安装lodash的类型声明,通过搜索可以获取源码和声明文件的安装方式
        在这里插入图片描述
4).自定义声明(模块声明)

什么情况下需要自己来定义声明文件呢?

  • 情况一:我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件;比如lodash
  • 情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;
  1. 安装ladash,但不安装声明文件

    npm i lodash
    
  2. 在src/main.ts文件中,引入 lodash 文件,会报错 ( 找不到lodash 的声明文件)

    import lodash from 'lodash'
    
  3. 在 src 文件夹下创建 lodashDeclare.d.ts 声明文件,用于自定义声明文件

    // 声明 lodash 
    declare module 'lodash'{
      // 声明 lodash 中的 join 方法
      export function join(arr: any[]): void
    }
    
  4. 在 src/main.ts 文件中可以使用 lodash 的 join 方法,如果需要用到 lodash 中的别的方法,需要继续在 lodashDeclare.d.ts 中声明

    import lodash from 'lodash'
    console.log(lodash.join(['abc', 'rrr']))
    
5).声明变量、方法和类
  1. 在 index.html 文件中创建变量、方法和类

    let userName = '张三'
    let userAge = 18
    
    function getDateTime() {
        return new Date()
    }
    
    //定义类
    function Person(name, age) {
        this.name = name
        this.age = age
    }
    
  2. 在 src/userDeclare.d.ts 文件中对变量、方法和类进行声明

    declare let userName: string
    declare let userAge: number
    
    declare function getDateTime(): Date;
    
    declare class Person {
      name: string
      age: number
      constructor(name: string, age: number)
    }
    
  3. 在 src/main.ts 中使用变量、方法和类

    console.log(userName)
    console.log(userAge)
    console.log(getDateTime())
    
    const p = new Person('李四', 25)
    console.log(p)
    
6).声明图片

在开发中使用了 jpg 这类图片文件,默认typescript也是不支持的,也需要对其进行声明;

  1. 在 src/img 文件夹中放入一个jpg格式的图片,并在 src/main.ts 文件中引入,会报一个 “找不到模块“./img/abc.jpg”或其相应的类型声明。” 的错误

    import myImg from './img/abc.jpg'
    
  2. 在文件 src/imgDeclare.d.ts 中对图片进行声明

    //声明 jpg 图片
    declare module '*.jpg'{
      const src: string
      export default src
    } 
    
    //也可以简写为
    // declare module '*.jpg'
    
6).声明文件

在开发vue的过程中,默认是不识别我们的.vue文件的,那么我们就需要对其进行文件的声明;

  • 声明文件
    // 声明 vue 文件
    declare module '*.vue' {
      import { DefineComponent } from 'vue'
      const component: DefineComponent
      export default component
    }
    
7).声明命名空间 (jquery声明)
  1. 在 index.html 文件中引入 jquery, 在ts文件中使用会报错
    <script src=" https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
    
  2. 在src/jqueryDeclare.d.ts 中对 jquery 进行声明
    declare namespace $ {
      //声明jquery中的ajax 方法
      export function ajax(settings: any): any
    }
    
  3. src/main.ts 中使用ajax
    $.ajax({})
    
转载请注明出处或者链接地址:https://www.qianduange.cn//article/10182.html
标签
评论
发布的文章

JSON&yaml和Properties

2024-06-06 10:06:54

JavaScript中的JSON.stringify()

2024-06-06 10:06:52

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!