Typescript 装饰器和反射

2023-11-16

装饰器

装饰器(也叫注解)对一个类/方法/属性/参数的装饰。它是对这一系列代码的增强,并且通过自身描述了被装饰的代码可能存在行为改变。

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

装饰器分为:类装饰器、方法装饰器、属性装饰器、参数装饰器

要使用装饰器需要在tsconfig.json中配置:

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

一个简单的示例:

在一个User类中含有一个changeName方法,在调用方法前,需要调用validate方法进行校验,此种方式缺点:侵入了changeName主流程代码,并且耦合性提高

class User {
    name: string;
 
    id: number;
 
    constructor(name: string, id: number) {
        this.name = name;
        this.id = id;
    }
 
    public validate(newName: string) {
        if (!newName) {
            throw Error("name is invalid");
        }
    }
 
    public changeName(newName: string) {
        this.validate(newName)
        this.name = newName;
    }
}

借助装饰器可以进行以下改造,不侵入代码,降低耦合度

const validate = (target: any,propertyKey: string,descriptor: PropertyDescriptor) => {
    let method = descriptor.value;
    descriptor.value = function(newValue: string) {
        if (!newValue) {
            throw Error("name is invalid");
        } else {
            method.apply(this, arguments)
        }
    }
}
class User {
    name: string;
 
    id: number;
 
 
    constructor(name: string, id: number) {
        this.name = name;
        this.id = id;
    }
 
    public validate(newName: string) {
        if (!newName) {
            throw Error("name is invalid");
        }
    }
 
    @validate
    public changeName(newName: string) {
        this.name = newName;
    }
}

装饰器工厂 - 可以传参的装饰器

function print(msg: string) {
    return function(traget: string, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(`msg: ` + msg)
    }
}
class User {
    @print('getName evaluated')
    public getName () {
        return 'name'
    }
}
 
// 调用getName时会输出: getName evaluated

类装饰器

类装饰器表达式会在运行时当做函数被调用,类的构造函数作为其唯一的参数

const sealed = (constructor: Function) {
    Object.seal(constructor)
    Object.seal(constructor.prototype)
}
 
@sealed
class User{}

方法装饰器

方法装饰器会在运行时当做函数被调用,有3个参数:

1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象

2、成员的名字

3、成员的属性描述符{ value: any, writable: boolean, enumerable: boolean, configurable: boolean }

示例参考示例1

访问器装饰器

和函数装饰器一样,只不过是装饰与访问器上

function print(msg: string) {
    return function(traget: string, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(`msg: ` + msg)
    }
}
class User {
    private _name: string
    @print('getName evaluated')
    get name () {
        return this._name
    }
}

属性装饰器

属性装饰器会在运行时当做函数被调用,有2个参数
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、成员的名字

const freeze = (target, propertyKey) => {
    target[propertyKey] = 0;
    Object.defineProperty(target, propertyKey, {
        configurable: false,
        writable: false,
    });
}
class User {
    @freeze
    id: number
}

参数装饰器

参数装饰器会在运行时当作函数被调用,有3个参数
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、成员的名字
3、参数在函数参数列表的索引

const required = function (target: any, propertyKey: string, paramterIndex: number) {
    console.log(target[propertyKey].prototype)
}
class User {
    private _name: string
    changeName (@required name: string) {
        this._name = name
    }
}

validate示例用在参数装饰器上

// 定义一个私有 key
const requiredMetadataKey = Symbol("required")
// 定义参数装饰器,大概思路就是把要校验的参数索引保存到成员中
const required = function (target, propertyKey: string, parameterIndex: number) {
    // 参数装饰器只能拿到参数的索引
    if (!target[propertyKey][requiredMetadataKey]) {
        target[propertyKey][requiredMetadataKey] = {}
    }
    // 把这个索引挂到属性上
    target[propertyKey][requiredMetadataKey][parameterIndex] = true
}
 
// 定义一个方法装饰器,从成员中获取要校验的参数进行校验
const validate = function (target, propertyKey: string, descriptor: PropertyDescriptor) {
    // 保存原来的方法
    let method = descriptor.value
    // 重写原来的方法
    descriptor.value = function () {
        let args = arguments
        // 看看成员里面有没有存的私有的对象
        if (target[propertyKey][requiredMetadataKey]) {
            // 检查私有对象的 key
            Object.keys(target[propertyKey][requiredMetadataKey]).forEach(parameterIndex => {
            // 对应索引的参数进行校验
                if (!args[parameterIndex]) throw Error(`arguments${parameterIndex} is invalid`)
            })
        }
    }
}
 
class User {
    name: string
    id: number
    constructor(name:string, id: number) {
        this.name = name
        this.id = id
    }
 
    // 方法装饰器做校验
    @validate
    changeName (@required newName: string) { // 参数装饰器做描述
        this.name = newName
    }
}

反射

反射实在运行时动态获取一个对象的一切信息:方法/属性等等,特点在于动态类型反推导,在typescript中,反射的原理是通过设计阶段对对象注入元数据信息,在运行阶段读取注入的元数据,从而得到对象信息。

使用时需要在tsconfig.json配置并且引入reflect-metadata

{
    "compilerOptions": {
        "target": "ES5",
        "emitDecoratorMetadata": true
    }
}

反射可以获取对象的:
1、对象的类型
2、对象/静态属性的信息(类型)
3、方法的参数类型,返回类型

默认注入的三种元数据:
1、design:type:成员类型
2、design:paramtypes:成员所有参数类型
3、design:returntype:成员返回类型
使用反射实现动态路由和自动注入生成

默认元数据示例

import "reflect-metadata";

const Prop = (): PropertyDecorator => {
    return (target, key: string) => {
        const _type = Reflect.getMetadata('design:type', target, key)
        console.log("type: ", _type) // type: [Function: String]
    }
}

const Method = (): MethodDecorator => {
    return (traget: any, propertyKey: string, descriptor: PropertyDescriptor) => {
        const _params = Reflect.getMetadata('design:paramtypes', traget, propertyKey)
        console.log('params: ', _params) // params:  [ [Function: String] ]
    }
}

const GetReturn = (): MethodDecorator => {
    return (traget: any, propertyKey: string, descriptor: PropertyDescriptor) => {
        const _return = Reflect.getMetadata('design:returntype', traget, propertyKey)
        console.log('return type: ', _return) // return type:  [Function: String]
    }
}

class TestReflect {
    @Prop()
    public name: String

    @GetReturn()
    public getName (): String {
        return this.name
    }

    @Method()
    public setName (name: String) {
        this.name = name
    }
}

上面示例拿到了返回,类型和参数,就可以做些略微高级的事

简单的自动注入

import 'reflect-metadata'
const Autowired = (target: any, key: string) => {
	const type = Reflect.getMetadata('design:type', target, key)
    return target[key] = new type()
}
 
class ServiceDemo {
    public getService () {
        console.log('aaa')
    }
}
 
class ReflectDemo {
 
    @Autowired
    serviceDemo: ServiceDemo
 
 
    testAutowired () {
        this.serviceDemo.getService()
    }
}
 
let reflectDemo = new ReflectDemo()
reflectDemo.testAutowired() // aaa

自动生成路由

import 'reflect-metadata'
 
const Prefix = (path: string): ClassDecorator => {
    return (target: any) => {
        Reflect.defineMetadata(Symbol.for('PREFIX'), path, target)
    }
}
 
const getMethodDecorator = (method: string) => (path: string): MethodDecorator => {
    return (target: any, key: string, descriptor: PropertyDescriptor) => {
        Reflect.defineMetadata(Symbol.for('PATH_METADATA'), path, descriptor.value)
        Reflect.defineMetadata(Symbol.for('METHOD_METADATA'), method, descriptor.value)
    }
}
 
const Get = getMethodDecorator('get')
const Post = getMethodDecorator('post')
 
 
@Prefix('/test')
class ReflectDemo {
 
    @Get('/get-method')
    getMethod () {
    }
 
    @Post('/post-method')
    postMethod () {
 
    }
 
}
 
const mapRoutes = (instance: Object) => {
    const prototype = Object.getPrototypeOf(instance)
 
    const methodNames = Object.getOwnPropertyNames(prototype).filter(item => item !== 'constructor' && item !== 'Function')
 
    const prefix = Reflect.getMetadata(Symbol.for('PREFIX'), prototype['constructor'])
    return methodNames.map(methodName => {
        const fn = prototype[methodName]
        if (isFunction(fn) {
            const route = Reflect.getMetadata(Symbol.for('PATH_METADATA'), fn)
            const method = Reflect.getMetadata(Symbol.for('METHOD_METADATA'), fn)
            return {
                route,
                method,
                fn,
                methodName
            }
        }
    })
}
 
mapRoutes(new ReflectDemo())

参考

从 JavaScript 到 TypeScript 4 - 装饰器和反射

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Typescript 装饰器和反射 的相关文章

随机推荐

  • 重新学javaweb---ServletContext

    WEB容器在启动时 它会为每个WEB应用程序都创建一个对应的ServletContext对象 它代表当前web应用 这个对象创建出来之后就一直在内存中驻留 代表当前的web应用 它可以通过我们上一篇介绍的ServletConfig对象获取
  • arm-linux-gcc char与signed char和unsigned char

    1 三者关系 1 ANSI C 提供了3种字符类型 分别是char signed char unsigned char 而不是像short int一样只有两种 int默认就是unsigned int 2 三者都占1个字节 3 signed
  • 不规则语法命名数据框tibble

    本题来自 R数据科学 第7章使用tibble实现简单数据框 4 在以下的数据框中练习如何引用不符合语法规则的变量名 a 提取名称为 1 的变量 b 绘制表示变量 1 和变量 2 关系的散点图 c 创建一个名称为 3 的新列 其值为列 2 除
  • make_heap(), pop_heap(), push_heap()用法

    对make heap pop heap push heap 的用法做个总结 make heap 生成堆 他有两个参数 也可以有三个参数 前两个参数是指向开始元素的迭代器和指向结束元素的下一个元素的迭代器 第三个参数是可选的 可以用伪函数le
  • Python爬虫——8-1.scrapy深度爬取案例—百思不得姐

    对于scrapy框架的使用 爬取数据 多次运行命令行也是比较头疼和麻烦的 这里建议Windows R键输入cmd进入命令行 切入至项目所在目录后执行scrapy shell url 命令 可以很直观的检测程序是否出错 如xpath匹配路径是
  • 蓝桥杯经验分享

    作为已经入码坑的一员 在开始接触的时候一窍不通 老师讲完之后自己再写一遍都写不出来的那种 很是沮丧 失落 后来到了大二 逐渐的找到了感觉 在学习了诸如C C java python之后 感觉诸多编程语言殊途同归 编者观点 学习编程语言入手最
  • IDEA文件右键创建New没有Create New Servlet的解决办法

    Author codepig16 interesting wtsb7 1 问题描述 创建了一个Javaweb项目但是在src中右键创建中 没有Servlet选项 如下图所示 2 解决方案 解决方案分为三步 笔者本人是在第三步有问题 估计大部
  • Qt中左侧列表与右侧窗口关联

    左边列表选项与右边窗体关联 QListWidget list new QListWidget this list gt insertItem 0 tr Window1 list gt insertItem 1 tr Window2 QLab
  • C++ 多线程学习(二) 互斥锁std::mutex

    在多线程中经常会遇到多个线程同时修改同一个变量的情况 这个时候如果不对线程进行一定约束 很可能会导致意想不到的结果 例如有两个线程1和线程2 线程2的输入是线程1的结果 很显然如果在主线程中同时开启了线程1和线程2 它们是同时运行的 会直接
  • 华为机考108题(c++)(52-61)

    HJ52 计算字符串的编辑距离 描述 Levenshtein 距离 又称编辑距离 指的是两个字符串之间 由一个转换成另一个所需的最少编辑操作次数 许可的编辑操作包括将一个字符替换成另一个字符 插入一个字符 删除一个字符 编辑距离的算法是首先
  • 顺序表详解 —— 初始化、销毁、打印、增加、删除、查找、修改

    文章目录 顺序表介绍 初始化顺序表 销毁顺序表 打印顺序表 增加数据 头插 尾插 指定下标位置插入 删除数据 头删 尾删 指定下标位置删除 查找数据 修改数据 顺序表介绍 顺序表是在计算机内存中以数组的形式保存的线性表 线性表的顺序存储是指
  • Spark基础知识(个人总结)

    声明 1 本文为我的个人复习总结 并非那种从零基础开始普及知识 内容详细全面 言辞官方的文章 2 由于是个人总结 所以用最精简的话语来写文章 3 若有错误不当之处 请指出 一 Spark概述 Spark模块 Core SQL Streami
  • js字符串转数字

    1 通过Number 函数传入一个合法的字符串参数 可以把字符串转换成一个十进制整数 十进制字符串可以带小数 其它进制我试过都不能有小数 否则返回NaN 11 其实也是调用的Number 11 进行减 乘 除运算时 字符串的自动转换也是调用
  • Redis数据类型常用命令

    Redis数据类型常用命令 Redis Key String 字符串 List 列表 Set 集合 Hash 哈希 Zset 有序集合 三种特殊数据类型 Geospatial地理位置 Hyperloglog 基数 Bitmap 位图 位存储
  • R语言-ggplot2柱状堆叠图

    导入数据 load D R futures user 2 dat 提取需要画图的数据 a futures user 2 c 1 2 5 对部分错误的数据进行修改 a province which a province 广西桂林 lt 广西
  • Node 16版本和 node-sass 兼容性问题

    我电脑上的node版本是16 13 1 运行刚克隆下来的项目时 npm install 报错 gyp err 百度之后确定是node sass版本兼容性问题 项目的package json文件版本如下 devDependencies nod
  • str.charAt(i);的作用

    在java中 有 String str leetcode 则 str charAt 0 为 l str charAt 1 为 e str charAt 2 为 e
  • 拉格朗日插值

    直接上公式 简单的讲 这个玩意就是在给你若干个 f xi yi 的结果 算出f k 的结果 最朴素的实现方法 验证下这个公式的结果 include
  • 【利用Python module计算程序运算时间】

    本文简介如何利用Python module来计算程序运算时间 内容概览 为什么要计算程序的运行时间 方法一 time module 方法二 timeit module 为什么要计算程序的运行时间 编写程序时 为了比较各种算法或优化算法 需要
  • Typescript 装饰器和反射

    装饰器 装饰器 也叫注解 对一个类 方法 属性 参数的装饰 它是对这一系列代码的增强 并且通过自身描述了被装饰的代码可能存在行为改变 装饰器是一种特殊类型的声明 它能够被附加到类声明 方法 访问符 属性或参数上 装饰器使用 expressi