TypeScript基础理解

js中的困扰

1,不请不楚的数据类型

<script setup lang="ts">
    let welcome="hello"
    welcome()  //飘红。报错 TypeError: welcome is not a function js不会提示
</script>

2.有漏洞的逻辑

<script setup lang="ts">
    const str=Date.now() % 2 ? '奇数': '偶数'
    if(str !== '奇数'){
        console.log("hello")
    }else if(str === "偶数"){ //永远不会进入到这里 逻辑有问题
        console.log("world")
    }
</script>

3,访问不存在的属性

<script setup lang="ts">
    const obj={width:10,height:15}
    const area=obj.width * obj.heigth; //不存在的属性,js不会提示
</script>

4,低级的拼写错误

<script setup lang="ts">
    const message="hello,world"
    message.toUperCase();  //单词写错了,js不会提示
</script>

TS静态类型检查
在代码运行前进行检查,发现代码的错误或不合理之处,减小运行时异常的出现的几率,这种检查叫静态类型检查
类型声明
来对变量或函数形参,进行类型声明:
在 :后面可以写字面量类型,不过实际开发中用的不多

<script setup lang="ts">
//来对变量或函数形参,进行类型声明:
    let a:string ,b:number,c:boolean
    a="hello"; b=99; c=true;
    console.log(a,b,c)
    function count(x:number,y:number):number{
        return x+y;
    }
    let result=count(1,2)
    console.log(result)
    //在 :后面可以写字面量类型,不过实际开发中用的不多
    let d:"hello"
    d="hello"  //这里只能是hello
</script>

类型推断
ts会根据我们的代码,进行类型推导,例如下面代码中的变量d,只能存储数字
但要注意,类型推断不是万能的,面对复杂类型时推断容易出错,所以尽量还是明确的编写类型声明!

<script setup lang="ts">
 let d=99;
 d=false  //不能将类型“boolean”分配给类型“number”
</script>

js中的数据类型

1,string 2,number 3,boolean 4,null 5,undefined 6,bigint 7,symbol 8,object
备注:其中object包含:Array,Function,Date,Error等…

ts中的数据类型

1,上述所有js类型,
2,六个新类型:1,any 2,unknown 3,never 4,void 5,tuple 6,enum
3, 两个用于自定义类型的方式:1,type 2,interface
注意点:在js中的这些内置构造函数:Number,String,Boolean,它们用于创建对应的包装对象,在日常开发时很少使用,在ts中也是同理,所以在ts中进行类型声明时,通常都是用小写的number,string,boolean

<script setup lang="ts">
let str1:string   //TS官方推荐的写法
str1="hello"
str1=new String("hello")  //报错  不能将类型“String”分配给类型“string” “string”是基元,但“String”是包装器对象。如可能首选使用“string”。

let str2 :String
str2="hello"
str2=new String("hello") //这个就不报错  这个几乎不写 没有必要
</script>

自动装箱的理解:js在必要时会自动将原始类型包装成对象,以便调用方法或访问属性

<script setup lang="ts">
//原始类型字符串
let str="hello"
//当访问str.length时,js引擎做了以下工作
let size=(function(){
    //1,自动装箱,创建一个临时的String对象包装原始字符串
    let tempStringObject=new String(str);
    //2.访问String对象的length属性
    let lengthValue=tempStringObject.length;
    //3,销毁临时对象,返回长度值
    //js引擎自动处理销毁对象销毁,开发者无感知
    return lengthValue;
})();
console.log(size); //输出
</script>

常用类型

1,any

的含义是:任意类型,一旦将变量类型限制为any,那就意味着放弃了对该变量的类型检查
注意点:any类型的变量,可以赋值给任意类型的变量

<script setup lang="ts">
let a:any  //显示any
a=12
a="hello"
a=true;
let b;
b=12  //隐示any
b="hello"
b=true;

let x:string
x=a
console.log(x)
</script>

2.unknown

的含义是:未知类型,可以理解为一个类型安全的any,适用于:不确定数据的具体类型
unknown会强制开发者在使用之前进行类型检查,从而提供更强的类型安全性
读取any类型数据的任何属性都不会报错,而unknown正好相反

<script setup lang="ts">
let a:unknown
a=12
a="hello"
a=true;

let x:string
x=a  //报错 不能将类型“unknown”分配给类型“string”
console.log(x)
//第一种 赋值
if(typeof a==='string'){
    x=a
}
//第二种 (断言)
x=a as string
//第三种(断言)
x=<string>a

let str1:string
str1="hello"
str1.toUpperCase() //无警告

let str2:any
str2="hello"
str2.toUpperCase() //无警告

let str3:unknown
str3="hello"
str3.toUpperCase() //警告 “str3”的类型为“未知”。
( str3 as string).toUpperCase() //无警告
</script>

3,never

的含义是:任何值都不是,简言之就是不能有值,undefined,null,‘’,0都不行
几乎不用never 去直接限制变量,因为没有意义
never一般是ts主动推断出来的
never也可用于限制函数的返回值

<script setup lang="ts">
let a:never
a=0   //报错 不能将类型“number”分配给类型“never”
a=undefined   //报错
a="";   //报错
a=null   //报错

function demo():never{
    throw new Error("程序运行异常!")
}

let b:string
b="hello"
if(typeof b==="string"){
    console.log(b.toUpperCase())
}else{
    console.log(b) //ts会推断出此处的b是never
}
</script>

4.void

通常用于函数返回值声明,含义:函数返回值为空,调用者也不应该依赖其返回值进行任何操作
注意:编码者没有编写return 去指定函数的返回值,所以logMessage函数是没有显式返回值的,但会有一个隐示返回值,就是undefined;即:虽然函数返回类型为void,但也是可以接受undefined的,简单记:undefined是void可以接受的一种“空”
那限制函数返回值时,是不是undefined和void就没区别呢? ——有区别,因为还有这句话:返回值类型为void的函数,调用者不应依赖其返回值进行任何操作!

<script setup lang="ts">
function logMessage(msg:string):void{
    console.log(msg)
    return undefined;
}
logMessage("hello")

function demo1():void{
    console.log("hello")
}
let result1=demo1()
if(result1){  //报错

}
function demo():undefined{
    console.log("hello")
}
let result=demo()
if(result){

}
</script>

理解void和undefined
void是一个广泛的概念,用来表达“空”,而undefined则是这种“空”的具体现实之一,因此可以说undefined是void能接受的“空”状态的一种具体形式。换句话说:void包含undefined,但void表达的语义超越了单纯的undefined,它是一种意图上的约定,而不仅仅是特定值的限定
总结:若函数返回类型为void,那么:
1,从语法上讲:函数是可以返回undefined的,至于显示返回,还是隐示返回,这无所谓
2,从语义上讲:函数调用者不应关心函数返回值,也不应该依赖返回值进行任何操作,即使返回了undefined值

5.object

object(小写)的含义是:所有非原始类型,可存储:对象,函数,数组等,由于限制的范围比较宽泛,在实际开发中使用的相对较少
Object(大写):除了undefined和null的任何值,由于限制范围实在太大,所以实际开发中使用频率极低

<script setup lang="ts">
let a:object  //能存储的类型是非原始类型
let b:Object; //能存储的类型是可以调用到Object方法的类型
a={}
a=[]
a=function(){}
a=new String("123")
class Person{}
a=new Person()

a=9  //报错
a=true   //报错

b={}
b=[]
b=function(){}
b=new String("123")
class Person1{}
b=new Person1()

b=9  
b=true  

b=null  //报错
b=undefined  //报错
</script>

声明对象类型
1.在实际开发中,限制一般对象,通常使用以下形式
2,索引签名:允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的,常用于:描述类型不确定的属性,(具有动态属性的对象)

<script setup lang="ts">
//中间的逗号 可以改成分号,不写都可以
let person:{name:string,age?:number,[qwe:string]:any}
person={name:"tom",age:18,gender:"男"}
</script>

声明函数类型
备注:ts中的=>在函数类型声明时表示函数类型,描述其参数类型和返回类型
js中的=>是一种定义函数的语法,是具体的函数实现
函数类型声明还可以使用:接口,自定义类型等方式

<script setup lang="ts">
let count:(a:number,b:number)=>number  //ts语法
count =function(x,y){
    return x+y
}
</script>

声明数组类型

<script setup lang="ts">
let arr:string[]
let arr2:Array<number>
arr=["hel","wor"]
arr2=[1,2]
</script>

备注:上述代码中Array<string>属于泛型

6,tuple

:元组(Tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同,元组用于精确描述一组值的类型,?表示可选元素

<script setup lang="ts">
let arr1:[string,number]
let arr2:[string,number?]
let arr3:[boolean,...string[]]
arr1=["hello",1]
arr2=["hoo"]
arr3=[true,"h","a"]
</script>

7.enum

:枚举可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护
如下代码的功能是:根据调用walk时传人的不同参数,执行不同的逻辑,存在的问题是调用walk时传参时没有任何提示,编码者很容易写错字符串内容,并且用于判断逻辑的up,down,left,right是连续且相关的一组值,那此时就特别适合使用枚举(enum)
1,数字枚举一种最常见的枚举类型,其成员的值会自动递增,且数字枚举还具备反向映射的特点,在下面代码的打印中,不难发现:可以通过值来获取对应的枚举成员名称

<script setup lang="ts">
function walk(str:string){
    if(str==="up"){
        console.log(1)
    }else if(str==="down"){
        console.log(1)
    }else if(str==="left"){
        console.log(1)
    }else if(str==="right"){
        console.log(1)
    }else{
        console.log(1)
    }
}
walk("up")
</script>

使用数字枚举完成刚才walk函数中的逻辑,此时我们发现:代码更加直观易读,而且类型安全,同时也更加易于维护

<script setup lang="ts">
enum Direction{
    Up=5, //也可以指定枚举成员的初始值,其后的成员值会自动递增
    Down,Left,Right
}
console.log(Direction)
function walk(str:Direction){
    if(str===Direction.Up){
        console.log(1)
    }else if(str===Direction.Down){
        console.log(1)
    }else if(str===Direction.Left){
        console.log(1)
    }else if(str===Direction.Right){
        console.log(1)
    }else{
        console.log(1)
    }
}
walk(Direction.Up)
</script>

字符串枚举:枚举成员的值是字符串

<script setup lang="ts">
enum Direction{
    Up="up",
    Down="down",Left="left",Right="right"
}
let dir:Direction=Direction.Up
console.log(dir)
</script>

常量枚举:是一种特殊枚举类型,它使用const关键字定义,在编译时会被内联,避免生成一些额外的代码
何为编译时内联:其实就是ts在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象,这可以减少生成的js代码量,并提高运行时性能

<script setup lang="ts">
const enum Direction{
    Up, Down,Left,Right
}
console.log(Direction.Down)
</script>

8.type

可以为任意类型创建别名,让代码更简洁,可读性更强,同时能更方便地进行类型复用和扩展
1,基本用法:类型别名使用type关键字定义,type后跟类型名称,例如下面代码中num是类型别名
2,联合类型(或的概念)是一种高级类型,它表示一个值可以是几种不同类型之一

<script setup lang="ts">
type num=number;
let price:num
price=100
type Status=number | string;
type Gender="男"|"女"
function printStatus(data:Status):void{
    console.log(data)
}
function printGender(data:Gender):void{
    console.log(data)
}
printStatus(404)
printStatus("404")
printGender("女")
</script>

3,交叉类型(并且的概念,合并):允许将多个类型合并为一个类型,合并后的类型将拥有所有被合并类型的成员,交叉类型通常用于对象类型

<script setup lang="ts">
type Area={
    height:number,
    width:number
}
type Address={
    num:number,
    cell:number,
    room:string
}
type House=Area & Address
const house:House={
    height:100,
    width:100,
    num:2,
    cell:3,
    room:'233'
}
</script>

9,一个特殊情况

使用类型声明限制函数返回值为void时,ts并不会严格要求函数返回空

<script setup lang="ts">
function demo():void{
    return undefined
}
demo()
type LogFunc=()=>void
const f1:LogFunc=function(){
    return 999
}
</script>

10.类

<script setup lang="ts">
class Person{
    //属性声明
    name:string
    age:number
    //构造器
    constructor(name:string,age:number){
        this.name=name
        this.age=age
    }
   //方法
   speak(){
     console.log(`我叫${this.name},今年${this.age}`)
   }
}
const p1=new Person("李四",12)
//继承
class Student extends Person{
    grade:string
    //构造器
    constructor(name:string,age:number,geade:string){
        super(name,age)
        this.grade=geade
    }
    study(){
        console.log(`我叫${this.name}`)
    }
    //方法
   override  speak(){
     console.log(`我是某某叫${this.name},今年${this.age}`)
   }
}
const s1=new Student("王五",12,"熬")
console.log(s1)
</script>

11.属性修饰符

public 公开的 可以被:类内部,子类,类外部访问
protected 受保护的 可以被:类内部,子类访问
private 私有的 可以被:类内部访问
readonly 只读属性 属性无法修改

属性的简写形式 public 修饰符

<script setup lang="ts">
//简写前
class Person{
    //属性声明
    name:string
    age:number
    //构造器
    constructor(name:string,age:number){
        this.name=name
        this.age=age
    }
}
//简写后 需要加修饰符
class Person1{
    //构造器
    constructor(public name:string,public age:number){}
}
</script>

protected 修饰符

<script setup lang="ts">
//简写后
class Person{
    //构造器
    constructor(protected name:string,protected age:number){ }
    protected getDetails(){
        return `我叫:${this.name},年龄是${this.age}`
    }
    introduce(){
        console.log(this.getDetails())
    }
}
class Student extends Person{
    study(){
        this.getDetails()
        console.log(`${this.name}选修`)
    }
}
const p1=new Person('tom',18)
p1.introduce()
</script>

private 修饰符

<script setup lang="ts">
//简写后
class Person{
    //构造器
    constructor(public name:string,public age:number,private IDCard:string){ }
    private getDetails(){
        return `我叫:${this.name},年龄是${this.age},编号是${this.IDCard}`
    }
    introduce(){
        console.log(this.getDetails())
    }
}
class Student extends Person{
    study(){
        console.log(`${this.name}选修`)
    }
}
const p1=new Person('tom',18,"31243254354")
p1.introduce()
</script>

readonly 修饰符

<script setup lang="ts">
//简写后
class Person{
    //构造器
    constructor(public name:string,public readonly age:number){ }
    private getDetails(){
        return `我叫:${this.name},年龄是${this.age}`
    }
 
}
const p1=new Person('tom',18)
p1.age=20  //无法为“age”赋值,因为它是只读属性  报错
console.log(p1)
</script>

12.抽象类 abstract

概述:抽象类是一种无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法,也可以写具体实现,抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法
简记:抽象类不能实例化,其意义是可以被继承,抽象类里可以有普通方法,也可以有抽象方法

通过以下场景,理解抽象类:
我们定义一个抽象类Package,表示所有包裹的基本结构,任何包裹都有重量属性weight,包裹都需要计算运费,但不同类型的包裹(如:标准速度,特快专递)都有不同的运费计算方式,因此用于计算运费的calculate方法是一个抽象方法,必须由具体的子类来实现

<script setup lang="ts">
//抽象类
abstract class Package{
    //构造器
    constructor(public weight:number){ }
    //抽象方法
    abstract  calculate():number
    //具体方法
    printPackage(){
        console.log(`包裹重量为:${this.weight}kg,运费为:${this.calculate()}`)
    }
}
class StandardPackage extends Package{
    constructor(
        weight:number,
        public  unitPrice:number
    ){super(weight)}
    calculate(): number {
        return this.weight*this.unitPrice
    }
}
class ExpressPackage extends Package{
    constructor(
        weight:number,
        public unitPrice:number,
        public additional:number
    ){super(weight)}
    calculate(): number {
        if(this.weight>10){
            return 10*this.unitPrice+(this.weight-10)*this.additional
        }else{
            return this.weight*this.unitPrice;
        }
    }
}
const s1=new StandardPackage(10,5)
s1.printPackage()
</script>

总结:何时使用抽象类?
1.定义通用接口:为一组相关的类定义通用的行为(方法或属性)时
2,提供基础实现:在抽象类中提供某些方法或为其提供基础实现,这样派生类就可以继承这些实现。
3,确保关键实现:强制派生类实现一些关键行为
4,共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复

13,interface(接口)

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

<script setup lang="ts">
//接口
interface PersonInterface{
    name:string,
    age:number,
    speak(n:number):void
}
//implements 实现的意思
class Person implements PersonInterface{
    constructor(
        public name:string,
        public age:number,
    ){}
    speak(n: number): void {
        for(let i=0;i<n;i++){
            console.log(`我叫${this.name},年龄是${this.age}`)
        }
    }
}
const p1=new Person("tom",18)
p1.speak(2)
</script>

定义对象结构

<script setup lang="ts">
//接口
interface UserInterface{
    name:string,
    readonly gender:string //只读属性
    age?:number  //可选属性
    run:(n:number)=>void
}
const user:UserInterface={
    name:"李四",
    gender:"男",
    age:18,
    run(n){
        console.log(`跳了${n}`)
    }
}
</script>

定义函数结构

<script setup lang="ts">
//接口
interface CountInterface{
    (a:number,b:number):number;
}
const count:CountInterface=(x,y)=>{
    return x+y
}
</script>

接口之间的继承

<script setup lang="ts">
//接口
interface PersonInterface{
    name:string,
    age:number
}
interface StrdentInterface extends PersonInterface{
    grade:string
}
const stu:StrdentInterface={
    name:"李四",
    age:18,
    grade:"男"
}
</script>

接口自动合并(可重复定义)

<script setup lang="ts">
//接口
interface PersonInterface{
    name:string,
    age:number
}
interface PersonInterface{
    grade:string
}
const stu:PersonInterface={
    name:"李四",
    age:18,
    grade:"男"
}
</script>

总结:何时使用接口?
1,定义对象的格式:描述数据模型,API响应格式,配置对象…等等,是开发中用的最多的场景
2,类的契约:规定一个类需要实现那些属性和方法
3,自动合并:一般用于扩展第三方库的类型,这种特性在大型项目中可能会用到

14,一些相似概念的区别

1,interface与type的区别
相同点:interface和type都可以用于定义对象结构,两者在许多场景中是可以互换的
不同点:interface:更专注于定义对象和类的结构,支持继承,合并
type:可以定义类型别名,联合类型,交叉类型,但不支持继承和自动合并

<script setup lang="ts">
//使用interface
interface PersonInterface{
    name:string;
    age:number;
    speak():void;
}
//使用type
type PersonType={
    name:string;
    age:number;
    speak():void;
}
let p1:PersonInterface={
    name:"李四",
    age:12,
    speak(){
        console.log(this.name)
    }
}
let p2:PersonType={
    name:"李四",
    age:12,
    speak(){
        console.log(this.name)
    }
}
</script>

2,interface与抽象类的区别
相同点:都用于定义一个类的格式(应该遵循的契约)
不同点:接口:只能描述结构,不能有任何实现代码,一个类可以实现多个接口
抽象类:即可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类

<script setup lang="ts">
//使用interface
interface FlyInterface{
    fly():void
}
interface SwimInterface{
    swim():void
}
class Duck implements FlyInterface,SwimInterface{
    fly(): void {
        console.log(1)
    }
    swim(): void {
        console.log(2)
    }
}

</script>

泛型

泛型允许我们在定义函数,类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性
举例:如下代码中就是泛型,(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型:
泛型函数

<script setup lang="ts">
function logData<T>(data:T){
    console.log(data)
}
logData<number>(1)
logData<string>("hello")
</script>

泛型可以有多个

<script setup lang="ts">
function logData<T,U>(data1:T,data2:U):T | U{
   return Date.now() % 2 ? data1 : data2
}
logData<number,boolean>(1,true)
logData<string,string>("hello","world")

</script>

泛型接口

<script setup lang="ts">
interface PersonInterface<T>{
    name:string,
    age:number,
    extraInfo:T
}
type JobInfo={
    title:string,
    num:number
}
let p:PersonInterface<number>={
    name:"李四",
    age:12,
    extraInfo:230
}
let p1:PersonInterface<JobInfo>={
    name:"李四",
    age:12,
    extraInfo:{
        title:'历史',
        num:20
    }
}
</script>

泛型类

<script setup lang="ts">
class Person<T>{
    constructor(
        public name:string,
        public age:number,
        public extraInfo:T
    ){}
    speak(){
        console.log(`我叫${this.name},年龄${this.age}`)
        console.log(this.extraInfo)
    }
}
const p1=new Person<number>("tom",12,23)
</script>

类型声明文件

是ts中的一种特殊文件,通常以.d.ts作为扩展名,它的主要作用是为现有的js代码提供类型信息,使得ts能够在使用这些js库或模块时进行类型检查和提示
demo.js

export function add(a,b){
    return a+b;
}
export function mul(a,b){
    return a*b;
}

demo.d.ts

//声明一个函数   declare声明
declare function add(a:number,b:number):number;
declare function mul(a:number,b:number):number;
export{add,mul}

index.ts

<script setup lang="ts">
import {add,mul} from "./demo.js"
console.log(add(1,2))
console.log(mul(2,3))
</script>

装饰器

是一种特殊的函数,它可以对:类,属性,方法,参数进行扩展,同时能让代码更简洁,需要开发者手动调整配置,来开启装饰器支持
在配置文件tsconfig.json 开启"experimentalDecorators": true,
装饰器有5种:类装饰器,属性装饰器,方法装饰器,访问器装饰器,参数装饰器

1,类装饰器

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

<script setup lang="ts">
/*
Demo函数会在Person类定义时执行
参数说明:target参数是被装饰的类,即:Person
*/
function Demo(target:Function){
    console.log(target)
}
//使用装饰器
@Demo  //等价于执行了Demo函数的调用,还把Person参数传入进去了
class Person{}
</script>

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

<script setup lang="ts">
/*
Demo函数会在Person类定义时执行
参数说明:target参数是被装饰的类,即:Person
*/
function Demo(target:Function){
    target.prototype.toString=function(){
        return JSON.stringify(this)
    }
    //封锁,不允许添加删除
    // Object.seal(target.prototype)
}
//使用装饰器
@Demo  //等价于执行了Demo函数的调用,还把Person参数传入进去了
class Person{
    constructor(public name:string,public age:number){}
}
const p1=new Person("李四",23)
console.log(p1.toString())
interface Person{
    x:number
}
Person.prototype.x=99
console.log(p1.x)
</script>

类装饰器有返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类
类装饰器无返回值:若类装饰器无返回值或返回undefined,那被装饰的类不会被替换

<script setup lang="ts">
/*
Demo函数会在Person类定义时执行
参数说明:target参数是被装饰的类,即:Person
*/
function Demo(target:Function){
    //装饰器有返回值时,该返回返回值会替换掉被装饰的类
    return class {
        test(){
            console.log(20)
        }
    }
}
//使用装饰器
@Demo  //等价于执行了Demo函数的调用,还把Person参数传入进去了
class Person{
    test(){
            console.log(10)
        }
}
console.log(Person)
</script>

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

<script setup lang="ts">
/*
 new  表示:该类型是可以用new操作符调用
 ...args  表示:构造器可以接受  任意数量 的参数
 any[]  表示:构造器可以接受  任意类型 的参数
 {}   表示:返回类型是对象(非null,非undefined的对象
*/
type Con=new (...args:any[])=>{}
//需求是fn得是一个类
function test(fn:Con){}
class Person {}
test(Person)
</script>

声明构造类型+指定静态属性

<script setup lang="ts">
/*
 new  表示:该类型是可以用new操作符调用
 ...args  表示:构造器可以接受  任意数量 的参数
 any[]  表示:构造器可以接受  任意类型 的参数
 {}   表示:返回类型是对象(非null,非undefined的对象
*/
type Con={
    new (...args:any[]):{}
    wife:string
}
//需求是fn得是一个类
function test(fn:Con){}
class Person {
  static wife="sed"
}
test(Person)
</script>

替换被装饰的类
对于高级一些的装饰器,不仅仅是覆盖一个原型上的方法,还要有更多功能,例如添加新的方法和状态
设计一个LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法用于读取创建时间

<script setup lang="ts">
type Con=new (...args:any[])=>{}
   
interface Person{
    getTime():void
}
function LogTime<T extends Con>(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("hello")}
}
const p1=new Person("历史",12)
console.log(p1.getTime())
</script>

2.装饰器工厂是一个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活地控制装饰器的行为
定义一个LogInfo类装饰器工厂,实现Person实例可以调用到introduce方法,且introduce中输出内容的次数,由LogInfo接收的参数决定

<script setup lang="ts">
type Con=new (...args:any[])=>{}
   
interface Person{
    introduce:()=>void
}
function LogInfo(n:number){
    return function(target:Function){
        target.prototype.introduce=function(){
            for(let i=0;i<n;i++){
                console.log(`我叫${this.name},年龄${this.age}`)
            }
        }
    }
}

@LogInfo(4)
class Person {
  constructor(public name:string,public age:number){}
  speak(){console.log("hello")}
}
const p1=new Person("历史",12)
p1.introduce()
</script>

装饰器组合,执行顺序为:先 由上到下 的执行所有的装饰器工厂,依次获取到装饰器,然后再 由下到上 执行所有的装饰器
执行顺序

<script setup lang="ts">
//装饰器
function test1(target:Function){
    console.log(1)
}
//装饰器工厂
function test2(){
    console.log(2+"工厂")
    return function(target:Function){
        console.log(2)
    }
}
//装饰器工厂
function test3(){
    console.log(3+"工厂")
    return function(target:Function){
        console.log(3)
    }
}
//装饰器
function test4(target:Function){
    console.log(4)
}
@test1
@test2()
@test3()
@test4
class Person {}
</script>

应用

<script setup lang="ts">
interface Person{
    introduce:()=>void
    getTime():void
}
//装饰器
function Demo(target:Function){
    target.prototype.toString=function(){
        return JSON.stringify(this)
    }
    //封锁,不允许添加删除
    Object.seal(target.prototype)
}
//装饰器工厂
function LogInfo(n:number){
    return function(target:Function){
        target.prototype.introduce=function(){
            for(let i=0;i<n;i++){
                console.log(`我叫${this.name},年龄${this.age}`)
            }
        }
    }
}
type Con=new (...args:any[])=>{}
//装饰器
function LogTime<T extends Con>(target:T){
    return class extends target{
        createdTime:Date
         constructor(...args:any[]){
            super(...args)
            this.createdTime=new Date()
         }
         getTime(){
            return `该对象的创建时间是:${this.createdTime}`
         }
    }
}

@Demo
@LogInfo(3)
@LogTime
class Person {
  constructor(public name:string,public age:number){}
  speak(){console.log("hello")}
}

const p1=new Person("李四",12)
p1.speak()
console.log(p1.toString())
p1.introduce()
console.log(p1.getTime())
</script>

2.属性装饰器

<script setup lang="ts">
/*
 参数说明: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
  }
}
</script>

关于属性遮蔽,当构造器中的this.age=age试图在实例上赋值时,实际上是调用了原型上age属性的set方法

<script setup lang="ts">
class Person {
   name:string
   age:number
  constructor(name:string,age:number){
      this.name=name
      this.age=age
  }
}
let value=120
//给指定对象添加属性defineProperty  
Object.defineProperty(Person.prototype,"age",{
    get(){
        return value
    },
    set(v){
        value=v
    }
})
const p1=new Person("李四",12)

console.log(p1)
</script>

举例:定义一个State属性装饰器,来监视属性的修改 有点问题打印不出来 不知道是不是版本的问题

<script setup lang="ts">
/*
 参数说明:target:对于静态属性来说值是类,对于实例属性来说值是类的原型对象
 propertyKey:属性名
*/
function State(target:object,propertyKey:string){
    let key=`__${propertyKey}`
    //给指定对象添加属性defineProperty  
    Object.defineProperty(target,propertyKey,{
        get(){
            return this[key]
        },
        set(v){
            console.log(`${propertyKey}最新值为${v}`)
            this[key]=v
        },
        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("老师",12)
console.log(p1)
</script>

3,方法装饰器

<script setup lang="ts">
/*
 参数说明:target:对于静态方法来说值是类,对于实例方法来说值是类的原型对象
 propertyKey:方法的名称
 descriptor:方法的描述对象,其中value属性是被装饰的方法
*/
function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){
 
   console.log(target,propertyKey,descriptor)
}
class Person {
  constructor(public name:string,public age:number){ }
  @Demo speak(){
    console.log(`我叫${this.name},年龄${this.age}`)
  }
  @Demo static isAdult(age:number){
    return age >=18
  }
}
const p1=new Person("老师",12)
p1.speak()
</script>

定义一个Logger方法装饰器,用于在方法执行前和执行后,均追加一些额外逻辑
定义一个Validate方法装饰器,用于验证数据

<script setup lang="ts">
/*
 参数说明: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(maxVaule:number){
   return function (target:object,propertyKey:string,descriptor:PropertyDescriptor){
  //存储原始方法
  const originnal=descriptor.value
  //替换原始方法
  descriptor.value=function(...args:any[]){
    //自定义的验证逻辑
    if(args[0]>maxVaule){
        throw new Error("年龄非法")
    }
    //如果所有参数都符合要求,则调用原始方法
    return originnal.apply(this,args);
  }
}
}

class Person {
  constructor(public name:string,public age:number){ }
  @Logger speak(str:string){
    console.log(`我叫${this.name},年龄${this.age},哈哈——${str}`)
  }
  @Validate(10) static isAdult(age:number){
    return age >=18
  }
}
const p1=new Person("老师",12)
p1.speak("hellp")
console.log(Person.isAdult(12))
</script>

4,访问器装饰器

<script setup lang="ts">
/*
 参数说明:target:对于实例访问器来说值是 所属类的原型对象  ,对于静态访问器来说值是 所属类
 propertyKey:访问器的名称
 descriptor:描述对象
*/
function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){
   console.log(target,propertyKey,descriptor)
}

class Person {
  @Demo
  get address(){
     return "下来"
  }
  @Demo
  static get country(){
    return "丰富"
  }
}
</script>

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

<script setup lang="ts">
/*
 参数说明:target:对于实例访问器来说值是 所属类的原型对象  ,对于静态访问器来说值是 所属类
 propertyKey:访问器的名称
 descriptor:描述对象
*/
function RangValidate(min:number,max:number){
  return function(target:object,propertyKey:string,descriptor:PropertyDescriptor){
    //保存原始的set
     const originalSetter=descriptor.set
     //重写setter
     descriptor.set=function(value:number){
        //检查设置的值是否在指定的最小值和最大值之间
        if(value< min || value > max){
            //如果值不在范围内,抛出错误
            throw new Error(`${propertyKey}的值应该在${min}${max}之间!`)
        }
        //如果值在范围内,其原始setter方法存在,则调用原始setter方法
        if(originalSetter){
            originalSetter.call(this,value);
        }
     }
}
}


class Weather {
  private _temp:number;
  constructor(_temp:number){
    this._temp=_temp
  }
  @RangValidate(-50,50)
  set temp(value){
    this._temp=value;
  }
  get temp(){
    return this._temp;
  }
}
const w1=new Weather(28)
w1.temp=900
console.log(w1)
</script>

5,参数装饰器

<script setup lang="ts">
/*
 参数说明:target:如果修饰的是 实例方法 的参数 target是类的 原型对象,  如果修饰的是 静态方法 的参数,target是类
 propertyKey:参数所在的方法名称
 parameterIndex:参数在函数参数列表中的索引 从0开始
*/
function Demo(target:object,propertyKey:string,parameterIndex:number){
    console.log(target,propertyKey,parameterIndex)
}

class Weather {
  constructor(public name:string){}
  speak(@Demo message1:any, message2:any){
    console.log(`${this.name}想对说:${message1},${message2}`)
  }
 
}

</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光浅止

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值