当前位置: 首页 > news >正文

TypeScript教程

目录

  • 1. TypeScript 简介
    • 1.1. JavaScript
    • 1.2. TypeScript
  • 2. 编译TypeScript
    • 2.1. 命令行编译
    • 2.2. 自动化编译(推荐)
  • 3.类型声明
  • 4. 类型
    • 4.1. 类型概括
    • 4.2. any
    • 4.3. unknown
    • 4.4. never
    • 4.5. void
    • 4.6. object
    • 4.7. tuple
    • 4.8. enum
    • 4.9. type
    • 4.10. 特殊情况
    • 4.11. class
    • 4.12. 属性修饰符
    • 4.13. 抽象类
    • 4.14. interface(接口)
    • 4.15. 一些相似概念的区别
  • 5. 泛型
  • 6. 类型声明文件
  • 7. 装饰器
    • 7.1. 类装饰器
    • 7.2. 装饰器工厂
    • 7.3. 装饰器组合
    • 7.4. 属性装饰器
    • 7.5. 方法装饰器
    • 7.6. 访问器装饰器
    • 7.7. 参数装饰器

1. TypeScript 简介

1.1. JavaScript

  javaScript 当年诞生时的定位是浏览器脚本语言,用于在网页中嵌入一些简单的逻辑,而且代码量很少。随着时间的推移,JavaScript 变得越来越流行,如今的 JavaScript 已经可以全栈编程了。现如今的 JavaScript 应用场景比当年丰富的多,代码量也比当年大很多,随便一个 JavaScript 项目的代码量,可以轻松的达到几万行,甚至十几万行。然而 JavaScript 当年“出生简陋”,没考虑到如今的应用场景和代码量,逐渐出现很多困扰。
  JavaScript 中的困扰:
  1. 不清不楚的数据类型

let welcome = 'hello'
welcome() //编译器没有提示,TypeError: welcome is not a function

  2. 有漏洞的逻辑

const str = Date.now() % 2 ? '奇数':'偶数'
if(str !== '奇数'){
    alert('是偶数') //只会弹出这一个,编译器没有提示
}else if (str === '偶数') {
    alert('是偶数')
}

  3. 访问不存在的属性

const obj = {w:10,h:8}
const area = obj.w * obj.hh //编译器没有提示

  4. 低级的拼写错误

const mes = 'hellow,world'
mes.toUperCase() /单词拼写错误,编译器没有提示

1.2. TypeScript

  1. TypeScript 由微软开发,是基于 JavaScript 的一个扩展语言。
  2. TypeScirpt 包含 JavaScript 的所有内容,即:TypeScript 是 JavaScript 的超集。
  3. TypeScript 增加了:静态类型检查、接口、泛型等很多现代开发特性,因此更适合在大型项目的开发。
  4. TypeScript 需要编译为 JavaScript,然后交给浏览器或其他 JavaScript运行环境执行。

静态类型检查:
  在代码运行前进行检查,发现代码的错误或不合理之处,减小运行时异常出现的几率,些种检查叫静态类型检查,即把运行时的错误前置。
同样功能,TypeScript 的代码量要大于 JavaScript,但 TypeScript 代码结构更加清晰,在后期代码维护中 TypeScript 却远胜于 JavaScript

2. 编译TypeScript

浏览器不能直接运行 TypeScript 代码,需要纊译为 JavaScript 再交由浏览器解析器执行

2.1. 命令行编译

要把 .ts 文件编译为 .js 文件,需要配置 TypeScript 的编译环境,步骤如下:

  1. 创建 demo.ts 文件,例如:
const person = {
	name:"张三",
	age:18
}
console.log(`我是${person.name},我今年${person.age}岁了`)
  1. 全局案装 TypeScript
npm i typescript -g
  1. 编译 .ts 文件
tsc demo.ts

2.2. 自动化编译(推荐)

  1. 创建 TypeScript 编译控制文件
tsc --init
# 1.工程中会生成 tsconfig.json配置文件,其中包含着很多编译时的配置。
# 2.观察发现,默认编译的 JS版本是ES7,可手动调整为其他版本
  1. 监视目录中的 .ts 文件变化
tsc --watch
  1. 优化,当编译出错时不生成 .ts 文件
tsc --noEmitOnError --watch
# 推荐在 tsconfig.json配置文件中改

3.类型声明

使用:对变量或函数形参,进行类型声明:

// 1. 具体类型
let a: string //字符串
let b: number //数值
let c: boolean //布尔值

//参数 x,y,函数返回值 必须是数值,
function demo(x: number, y: number): number {
    return x + y
}

// 2. 字面量类型
let d: 'hello' //d的值只能为字符串“hello”
d = 'hello'

TS 会根据我们的代码,进行类型推导

let a = 100 //TS会推断变量a是数字,但是推荐编写类型声明

4. 类型

4.1. 类型概括

JavaScript 数据类型
string, number, boolean, null, undefined, bigint, symbol, object
备注:object 包含:Array, Function, Date, Error 等

TypeScript 数据类型

  1. 所有 JavaScript 类型
  2. 6个新类型:any, unknown, never, void, tuple, enum
  3. 2个自定义类型方式: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'
console.log(str2)
console.log(typeof str1)
str2 = new String('hello')
console.log(typeof str2)
console.log(str2)
  1. 原始数据 VS 包装对象
    • 原始数据:如 number, string, boolean, 在 JavaScript 中是简单数据类型,它们在内存中占用空间少,处理速度快。
    • 包装对象:如 Number对象、String对象、Boolean对象,是复杂类型,在内存中占用空间多,在日常开发时很少由开发人员自已创建包装对象。
  2. 自动装箱: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.销毁临时对象,返回长度(JavaScript引擎会自动处理对象销毁,开发者无感知)
    return lengthValue
})()
console.log(size)

4.2. any

any:任意类型,一旦将变量类型限制为any,即放弃对该变量的类型检查

// 明确表示a类型为 any [显示any]
let a: any
a = 100
a = 'hello'

// 没有表示类型,推断为 any [隐示any]
let b
b = 100
b = 'hello'

// any类型的变量,可以赋值给任意类型的变量
let c: any
c = 9
let d: string
d = c

4.3. unknown

unknown:未知类型

  1. unknown 可以理解为一个类型安全的 any, 适用不确定数据的具体类型
  2. unknown 会强制开发者在使用之前进行类型检查,从而提供更强的类型安全。
  3. 读取 any 类型数据的任何属性都不会报错,而 unknown 正好与之相反。
// 设置a的类型为 unknown
let a: unknown
a = 100
a = 'hello'
let b: string
// b = a 不能将类型"unknown"分配给类型"string"
// 1.类型判断
if(typeof a === 'string'){
    b = a
}
// 2.断言
b = a as string
b = <string> a

b.toUpperCase()//无警告
let c: any = "hello"
c.toUpperCase()//无警告
let d: unknown
// d.toUpperCase() //警告
(d as string).toUpperCase()

4.4. never

never:任何值都不是,就是不能有值,uedefined,null, “”, 0 都不行

  1. 几乎不用 never 去直接限制变量,因为没有意义。
  2. never 一般是 TypeScript 主动推断出来的
  3. never 也可用于限制函数的返回值
// 指定a的类型为never,即a以后不能存任何数据了
let a:never
// a = 1 //不能赋值

let b:string
b = 'hello'
if(typeof b === 'string'){
    console.log(b.toUpperCase())
}else {
    console.log(b) // TypeScript 会推断出此出的a是never,因为没有任何一个值符合此处逻辑
}

// 限制throwError函数需要任何返回值,任何值都不行
// 没有返回值函数,执行完默认返回 unddefined
function throwError(str: string):never{
    throwError("程序异常退出"+str)
}

4.5. void

void 与 undefined
void 是一个广泛的概念,用来表达“空”,而 undefined 则是这种“空”的具体表现实现之一,因此可以说 undefined 是 void 能接受的“空”状态的一种具体形式。
也就是说:void 包含 undefined ,但 void 表达的语义超越了单纯的 undefined ,它是一种意图上的约定,而不仅仅是特定值的限定。
总结:
void 返回类型的 函数,虽然返回 undefined ,但无所谓,不应该管,不应该用。

//void 通常用于函数返回值声明,含义:函数不返回任何值,调用者也不应依赖其返回值进行任何操作
function logMessage(msg:string):void{
    console.log(msg)
    // return undefined // ok
    // return; //ok
    /*
    没有 return ,函数没有 显式返回值,
    但会有一个 隐式返回值:undefined;
    即:虽然函数返回类型为void,但也可以接受 undefined.
    */
}
logMessage('hellohello')// 调用时,不应该接收返回值,就算接收了,这个返回值也不能用

function logMessage2(msg:string):undefined{

}
let res = logMessage2("hello") //调用时,可接收返回值,可使用
if(res){

}

4.6. object

object 与 Object 实际开发中不用,因为范围太大了

  1. object(小写):所有非原始数据,即对象,函数,数组等
  2. Object(大写):所有可以调用 Object 方法的类型,即除了 undefined、null
let a: object
a = {name:'张三'}
a = [1,2]
a = function(){}
a = new String('hello')
class Person{}
a = new Person()

let b: Object
b = 1

声明对象类型

//限制person对象必须有name属性,age为可选属性
let person: { name: string, age?: number }

//也可用分号
let person2: { name: string; age?: number }

//也可用换行
let person3: {
    name: string
    age?: number
}
person = { name: "张三", age: 18 }
person = { name: '张三' }

//索引签名:允许定对象可以具有任意数量的属性,这些属性的键和类型是可变的
let person4: {
    name: string
    age: number
    [key: string]: any //索引签名,可以不用key,只要占位
}
person4 = { name: '张三',age: 18, gender: '男' }

声明函数类型

// TypeScript 中的 => 在函数类型声明时表示函数类型,描述其参数类型和返回类型
// JavaScript 中的 => 是一种定义函数的语法,是具体的函数实现
let count: (a:number,b:number) =>number //a,b 相当于占位符,可以是别的值
count = function(w,h){
    return w*h
}

声明数组类型

let arr1: string[]
let arr2: Array<number> // 泛型
arr1 = ['a','b']
arr2 = [1,2]

4.7. tuple

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

let arr1:[string,number]
let arr2:[string,number?]
let arr3:[string,...string[]] //...表示任意数量的字符串类型

arr1 = ['hello',100]

4.8. enum

枚举(enum)可以定一组命名常量,增强代码可读性

function walk(str:string){
    if(str === 'up'){
        console.log('up')
    }else if(str === 'down'){
        console.log('down')
    }else {
        console.log('other')
    }
}
walk('down')//walk函数,参数是字符串,写错没有提示,up、down 是连续相关的一组值,适合使用 枚举(enum)

//改为

// 1.数字枚举
enum Direction {
    Up,
    Down
}
console.log(Direction) //{0: 'Up', 1: 'Down', Up: 0, Down: 1}
function walk2(data:Direction){
    if(data === Direction.Up){
        console.log('Up')
    }else if(data == Direction.Down){
        console.log('Down')
    }else{
        console.log('other')
    }
}
walk2(Direction.Up)

// 2.字符串枚举
enum Direction2 {
    Up = 'up',
    Down = 'down'
}
console.log(Direction2) // {Up: 'up', Down: 'down'} 丢失反向映射
// 3.常量枚举
// 使用 Const 关键字定义,在编译时会被 内联,避免生成一些额外代码
// 内联:TypeScript 编译时,会将枚举成员引用替换成它们的实际值,减少代码量
const enum Direction3 {
    Up,
    Down
}
console.log(Direction3.Up)


4.9. type

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

  1. 基本用法
// 类型别名使用 type 关键字定义,type 后跟类型名称,
type num = number
let price: num
price = 100
  1. 联合类型
// 联合类型是一种高级类型,它表示一个值可以是几种不同类型之一
type Status = number | string // | 叫管道符
type Gender = '男' | '女'

let sValue: Status = 100
let gValue: Gender = '男'
  1. 交叉类型
//交叉类型 允许将多个类型合并为一个类型,合并后的类型将拥有所有合并类型的成员,交叉类型通常用于对象类型
type Area = {
    h:number
    w:number
}
type Address = {
    cell:number
    room:string
}
type House = Area & Address
 const house:House = {
    h:100,
    w:100,
    cell:200,
    room:"702"
 }

4.10. 特殊情况

在函数定义时,限制函数返回值为 void,那么该函数的返回值就必须 undefined

function demo():void{
    // return undefined
}

特殊情况:使用类型声明限制函数返回值为 void 时,TypeScript 并不会严格要求函数值

type DemoFunc = () => void
const f1: DemoFunc = () => { }
const f2: DemoFunc = () => {
    return 100 //允许返回 非undefined
}

原因:是为了确保如下代码成立,Array.prototype.push 的返回一个数字,而 Array.prototype.forEach 方法期望其回调的返回类型是 void

const src = [1, 2, 3]
const dst = [0]
src.forEach((el) => dst.push(el))

4.11. class

类 class
Student 继承 Person

class Person {
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    speak(){
        console.log(`我叫:${this.name},今年${this.age}`)
    }
}
let p = new Person('张三',19)
console.log(p)
p.speak()

class Student extends Person{
    grade:string
    constructor(name:string,age:number,grade:string){
        super(name,age)
        this.grade = grade
    }
    study(){
        console.log("我在学习中")
    }
    // override 表示重写
    override speak(): void {
        console.log(`我叫:${this.name},今年${this.age}岁,上${this.grade}`)
    }
}

4.12. 属性修饰符

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

public 修饰符

class Person {
    public name: string
    public age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    public speak(){
        //类内部
        console.log(`我叫:${this.name},今年${this.age}`)
    }
}

class Student extends Person{
    study(){
        // 子类
        console.log(`${this.name}我在学习中`)
    }
}

//类外部
const p = new Person('张三',19)
console.log(p.age)

属性的简写形式

class Person {
    public name: string
    public age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
}
//简写
class Person2{
    constructor(public name:string, public age:number){}
}

protected 修饰符

class Person {
    constructor(protected  name: string, protected  age: number) {}
    protected speak(){
        //类内部
        console.log(`我叫:${this.name},今年${this.age}`)
    }
}

class Student extends Person{
    study(){
        // 子类
        console.log(`${this.name}我在学习中`)
    }
}

//类外部不能使用
const p = new Person('张三',19)
// console.log(p.age)

private 修饰符

class Person {
    constructor(protected name: string,private age: number) {}
    protected speak(){
        //类内部可以使用
        console.log(`我叫:${this.name},今年${this.age}`)
    }
}

class Student extends Person{
    study(){
        // 子类不能使用
        // console.log(`我在学习中,今年${this.age}岁`)
    }
}

//类外部不能使用
const p = new Person('张三',19)
// console.log(p.age)

readonly 修饰符

class Person {
    constructor(readonly name: string, private age: number) {}
}

//readonly 不能修改
const p = new Person('张三',19)
// p.name = "李四"

4.13. 抽象类

概述:抽象类相当于对子类的一种约束。
特点:

  1. 不能实例化,只能被继承
  2. 抽象方法必被子类重新实现
//对人进行约束(抽象类)
abstract class Person {
    //构造属性
    constructor(public name: string, public age: number) { }
    //所有人必须要吃(抽象方法)
    abstract eat():string
}
// 学生继承人类
class Student extends Person{
    constructor(name:string,age:number,public book:string){
        super(name,age)
    }
    override eat(){
        return `${this.name}看完书:${this.book},就去吃饭`
    }
}

const s = new Student('张三',18,'三国')
console.log(s.eat())

4.14. interface(接口)

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

定义类结构

// PersonInterface接口,用于限制Person类的格式
interface PersonInterface {
    name: string
    age: number
    speak(n: number,m:number): void
}
// 定义一个类 Person,实现 PersonInterface 接口
class Person implements PersonInterface {
    constructor(public name:string,public age:number){}
    // 实现接口的 speak 方法,注意:实现speak时参数个数可以少于接口中的规定,但不能多
    speak(n: number): void {
        for(let i = 0; i< n;i++){
            console.log(`我是${this.name},今年${this.age}岁了`)
        }
    }
}
const p1 = new Person('张三',14)
p1.speak(4)

定义对象结构

interface UserInterface {
    name:string
    readonly gender:string //只读属性
    age?:number //可选属性
    run(n:number):void
}
const user:UserInterface = {
    name:'张三',
    gender:'男',
    run(n){
        console.log(`跑了${n}`)
    }
}

定义函数结构

interface SumInterface {
    (a:number,b:number):number
}
const sum:SumInterface = (x,y)=>{
    return x + y
}

接口之间的继承

interface PersonInterface {
    name:string
    age:number
}
interface StudentInterface extends PersonInterface {
    grade: string
}

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

interface PersonInterface {
    name: string
    age: number
}
interface PersonInterface {
    gender: string
}
const p:PersonInterface = {
    name:'tom',
    age:18,
    gender:'男'
}

总结:何时使用接口?

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

4.15. 一些相似概念的区别

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

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

5. 泛型

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

泛型函数

// <T> 就是泛型,可以写别的,设置泛型后即可在函数中使用 T 来表示该类型
function logData<T>(data: T) {
    console.log(data)
}
logData<number>(100)
logData<string>('hello')

泛型可以是多个

function logData<T,U>(data: T,data2: U) : T | U{
    console.log(data,data2)
    return Date.now() % 2 ? data1 : data2
}
logData<number,string>(100,'word')
logData<string,string>('hello','word')

泛型接口

interface PersonInterface<T> {
    name:string
    age:number
    info:T
}

let p:PersonInterface<number> = {
    name:'张三',
    age:18,
    info:14
}

6. 类型声明文件

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

demo.js

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

demo.d.ts

declare function add(a: number, b: number): number;
declare function mul(a: number, b: number): number;
export { add, mul };

index.ts

import { add, mul } from './demo.js'
console.log(add(1, 2))
console.log(mul(3, 4))

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>index</title>
    </head>
    <body>
        <script type="module" src="./index.js"></script>
    </body>
</html>

7. 装饰器

概述:

  1. 装饰器本质是一种特殊的函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁。
  2. 装饰器自 2015年在 ECMAScript-6 中被提出。
  3. 装饰器依然是实验特性,需要开发者手动调配置,来开启装饰器支持。
  4. 装饰器有 5 种:类装饰器、属性装饰器、方法装饰器、访问器装饰器、参数装饰器

备注:TypeScript5.0 中可以直接使用 类装饰器 ,但为了确保其他装饰器可用,现阶段使用时,仍建议使用 experimentalDecorators 配置来开启装饰器支持,在未来的版本中,官方会进一步调整装饰器的相关语法

7.1. 类装饰器

  1. 基本语法
    类装饰器是一个应用在 类声明上 的函数,可以为类添加客同的功能,或添加额外的逻辑。
/*
    Demo函数会在Person类定义时执行
    参数说明:
        target参数是被装饰的类,即:Person
*/
function Demo(target:Function){
    console.log(target)
}
@Demo
class Person{}
  1. 应用举例
//需求:定义一个装饰器,实现Person 实例调用 toString 时返回 JSON.stringfy 的结果
//使用装饰器重写toString方法 + 封闭其原型对象
function CustomString(target:Function){
    //向装饰器类的原型上添加自定义的 toString 方法
    //prototype:原型
    target.prototype.toString = function () {
        return JSON.stringify(this)
    }
    //封闭其原型对象,禁止随意操作其原型对象
    Object.seal(target.prototype)
}
//使用 CustomString 装饰器
@CustomString
class Person {
    constructor(public name:string){}
    speak(){
        console.log("hello word")
    }
}

let p = new Person("张三")
//测试输出
console.log(p.toString())
//禁止操作原型对象
interface Person {
    x:any
}
// Person.prototype.x = 100
// console.log(p.x)
  1. 关于返回值
    类装饰器有返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类。
    类装饰器无返回值:若类装饰器无返回值或返回undefined,那被装饰的类不会被替换
function Demo(target: Function) {
    //装饰器有返回值时,该返回值会被替换掉装饰的类
    return class {
        test() {
            console.log(2000)
        }
    }
}
class Person {
    test() {
        console.log(100)
    }
}
console.log(Person)
  1. 关于构造类型
    在TypeScript中,Function类型所表示的范围十分广泛,包括:普通函数、简头函数、方法等。但并非 Function 类型的函数都可以被 new 关键字实例化,例如箭头函数是不能被实例化的,那么TypeScript 中该如何声明一个构造类型呢?有以下两种方式:
//仅声明构造类型
/*
    new     表示该类型可以用new操作符调用
    ...args 表示构造器可以接收任意数量的参数
    any[]   表示构造器可以接收任意类型的参数
    {}      表示返回类型是对象(非null、非undefined的对象)
*/
type Constructor = new (...args: any[]) => {};
function test(fn: Constructor) { }
class Person { }
test(Person)
//声明构造类型 + 指定静态属性
//定义一个静态类型,且包含一个静态属性 wife
type Constructor = {
    new(...args: any[]): {};//构造签名
    wife: string; //wife属性
}
function test(fn: Constructor) { }
class Person {
    static wife = 'qwe'
}
test(Person)
  1. 替换被装饰的类
    对于高级一些的装饰器,不仅仅是覆盖一个原型上的方法,还要有更多功能,例好添加新的方法和状态。
//设计一个LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法用于读取时间。
//User接口
interface User {
    getTime():Date
    log():void
}
//自定义类型Class
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 User{
    name:string
    age:number
    constructor(name:string,age:number){
        this.name = name
        this.age = age
    }
    speak(){
        console.log('你好')
    }
}
const u = new User('张三',18)
console.log(u)

7.2. 装饰器工厂

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

//需求:定义一个LogInfo类装饰器工厂,实现Person实例可以调用到introduce方法,且introduce中输出内容的次数,由LogInfo接收的参数决寂静
interface Person {
    introduce:() => void
}

//定义一个装饰器工厂 LogInfo 它接收一个参数 n 返回一个类装饰器
function LogInfo(n:number){
    //装饰器函数 target 是被装饰的类
    return function(target:Function){
        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('hello')
    }
}

const p1 = new Person('张三',18)
p1.introduce()

7.3. 装饰器组合

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

//装饰器组合-执行顺序
//装饰器
function test1(target: Function) {
    console.log('test1')
}
//装饰器工厂
function test2() {
    console.log('test2工厂')
    return function (target: Function) {
        console.log('test2')
    }
}
//装饰器工厂
function test3() {
    console.log('test3工厂')
    return function (target: Function) {
        console.log('test3')
    }
}
//装饰器
function test4(target: Function) {
    console.log('test4')
}


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

//装饰器组合-应用
//处定义类型Class
type Constructor = new (...args: any[]) => {}

interface Person {
    introduce(): void
    getTime(): void
}

//使用装饰器重写toString方法 + 封闭原型对象
function customToString(target: Function) {
    //向被装饰类的原型上添加处定义的toString 方法
    target.prototype.toString = function () {
        return JSON.stringify(this)
    }
    //封闭其原型对象,禁止随意操作其原型对象
    Object.seal(target.prototype)
}

//创建一个装饰器,为类添加日志功能和创建时间
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}`
        }
    }
}

//定义一个装饰器工厂 LogInfo,它接受一个参数 n, 返回一个类装饰器
function LogInfo(n: number) {
    //装饰器函数, target 是被装饰的类
    return function (target: Function) {
        target.prototype.introduce = function () {
            for (let i = 0; i < n; i++) {
                console.log(`我的名字:${this.name},我的处龄:${this.age}`)
            }
        }
    }
}

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

const p = new Person('张三', 18)
console.log(p.toString())
p.introduce()
console.log(p.getTime())

7.4. 属性装饰器

  1. 基本语法
/*
    参数说明:
    targert: 对于静态属性来说值是类,对于实例属性来说值是类的实例对象
    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
    }
}

const p1 = new Person('张三',18)
  1. 关于属性遮蔽
//在构造器中的 this.age = age 试图在实例上赋值时,实际上是调用了原型上age属性的set方法
class Person {
    name:string
    age:number
    constructor(name:string,age:number){
        this.name = name
        this.age = age
    }
}
const p1 = new Person('张三',18)

//使用 defineProperty 给 Person原型添加age属性,并配置对应用的get与set
let value = 99
Object.defineProperty(Person.prototype,'age',{
    get(){
        return value
    },
    set(val){
        value = val
    }
})

const p2 = new Person('李四',19)
console.log(p2.age)//19
console.log(Person.prototype.age) //19
console.log(p2)
console.log(p1)
  1. 应用举例
//需求:定义一个 State 属性装饰器,来监视属性的修改。
//声明一个装饰器函数 State, 用于捕获数据的修改
function State(target:object, propertyKey:string){
    //存储属性的内部值
    let key = `__${propertyKey}`
    //使用Object.defineProperty 替换类的原始属性
    //重新定义属性,使其使用自定义的 getter 和 setter
    Object.defineProperty(target,propertyKey,{
        get(){
            return this[key]
        },
        set(newValue:string){
            console.log(`${propertyKey}的最新值为:${newVal}`)
            this[key] = newVal
        },
        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('张三',18)
const p2 = new Person('李四',19)

p1.age = 30
p2.age = 40
console.log(p1.age)
console.log(p2.age)

7.5. 方法装饰器

  1. 基本语法
/*
    参数说明:
    target: 对于静态方法来说值是类,对于实例方法来说值是原型对象。
    propertyKey: 方法名称。
    descriptor: 方法的描述对象,其中value属性是被装饰的方法。
*/
function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){
    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}`)
    }
    @Demo static isAudit(age:number){
        return age>=18
    }
}
const p1 = new Person('张三',18)
p1.speak()
  1. 应用举例
/*
需求:
    1.定义一个Logger方法装饰器,用于在方法执行前和执行后,均追加一些额外逻辑。
    2.定义一个Validate方法装饰器,用于验证数据
*/
function Logger(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
    //保存原始方法
    const original = descriptor.value;
    //替换原始方法
    descriptor.value = function (...args: any[]) {
        console.log(`${propertyKey}开始执行......`)
        const result = original.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() {
        console.log(`你好,我的名字:${this.name},我的年龄:${this.age}`)
    }
    @Validate(120) static isAudit(age: number) {
        return age >= 18;
    }
}

const p1 = new Person('张三', 18)
p1.speak()
console.log(Person.isAudit(100))

7.6. 访问器装饰器

  1. 基本语法
/*
    参数说明:
    target: 1.对于实例访问器来说值是【所属类的原型对象】
            2.对于静态访问器来说值是【所属类】
    propertyKey: 访问器的名称
    descriptor: 描述对象
*/
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 contry(){
        return '国家'
    }
}
  1. 应用举例
/*
需求:对Weather类的temp属性的set访问器进行限制,设置的最低温度-50,最高温度 50
*/
function RangeValidate(min: number, max: number) {
    return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
        //保存原始的 setter 方法,以便在后续调用中使用
        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
    }
    //设置温度范围在-50 到 50 之间
    @RangeValidate(-50, 50)
    set temp(value) {
        this._temp = value
    }
    get temp() {
        return this._temp
    }
}

const w1 = new Weather(25)
console.log(w1)
w1.temp = 67
console.log(w1)

7.7. 参数装饰器

  1. 基本语法
/*
    参数说明:
        target:
            1.如果修饰的是【实例方法】的参数,target 是类的【原型对象】
            2.如果修饰的是【静态方法】的参数,target 是类。
        propertyKey: 参数所在方法的名称
        parameterIndex: 参数在函数参数列表中的索引,从0开始
*/
function Demo(target: object, propertyKey: string, parameterIndex: number) {
    console.log(target)
    console.log(propertyKey)
    console.log(parameterIndex)
}

//类定义
class Person {
    constructor(public name: string) { }
    speak(@Demo message1: any, message2: any) {
        console.log(`${this.name}想说:${message1},${message2}`)
    }
}

相关文章:

  • python爬虫可能遇到的小bug
  • 【RK3588嵌入式图形编程】-SDL2-渲染文本
  • CSS 文档流:元素排列的底层逻辑与布局控制
  • stm32-ADC
  • 明远智睿SD2351核心板:多接口融合,破解边缘计算难题
  • 关于强化学习小记
  • 玩转 SpringCloud - 快速构建分布式系统详解
  • 第十六届蓝桥杯康复训练--5
  • 排列与二进制
  • 力扣刷题记录-二叉树展开为链表
  • 【2025】基于springboot+vue的教务/课程/成绩管理系统设计与实现(源码、万字文档、图文修改、调试答疑)
  • Agent Team 多智能体系统解析
  • Python 魔术方法深度解析:__getattr__ 与 __getattribute__
  • Springboot之RequestContextHolder 学习笔记
  • CANFD芯片在辐射环境中的技术演进
  • 【数学建模】最大最小值模型详解
  • Spring的基本用法
  • 服务器负载均衡
  • 机器学习——Numpy的神奇索引与布尔索引
  • JWT 认证机制
  • 邯郸一酒店办婚宴发生火灾,新郎母亲:饭没吃成酒店还要收费
  • 扶桑谈|素称清廉的石破茂被曝受贿,日本政坛或掀起倒阁浪潮
  • 著名国际关系理论家、“软实力”概念提出者约瑟夫•奈逝世
  • 华为招聘:未与任何第三方开展过任何形式的实习合作
  • 60岁济南石化设计院党总支书记、应急管理专家李有臣病逝
  • “子宫内膜异位症”相关论文男性患者样本超六成?福建省人民医院展开调查