装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。 装饰器使用@expression
这种形式,expression必须是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
Typescript中的装饰器是一项实验性功能,需要在tsconfig.json中开启该特性
1 2 3 4 5 | { "compilerOptions" : { "experimentalDecorators" : true } } |
例如,有一个@sealed
装饰器,我们这样定义sealed
:
1 2 3 | function sealed (target: any) { // 操作被装饰对象 } |
装饰器工厂
如果需要给装饰器添加一些动态行为,比如开发一个监控统计的装饰器,需要传入当前统计的事件名称,有多个事件名称时只需要变更传入的事件名而不用重复定义装饰器。
这时候需要使用到装饰器工厂。装饰器工厂也是一个函数,只不过它的返回值是一个装饰器。例如如下的事件监控装饰器:
1 2 3 4 5 | function event (eventName: string ) { return function(target: any) { // 获取到当前eventName和被装饰对象进行操作 } } |
装饰器组合
多个装饰器可以同时应用到被装饰对象上,例如下面的例子:
1 2 3 4 5 | @ sealed @test( 'test' ) class Demo { } |
装饰器执行顺序:
- 装饰器工厂需要先求值,再装饰,求值顺序是由上到下
- 装饰器可以直接求值,装饰顺序是由下到上
上面的说明可以难以理解,下面举一个实际的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function f() { console.log( 'f求值' ); return function(target: any) { console.log( 'f装饰' ); } } function g() { console.log( 'g求值' ); return function(target: any) { console.log( 'g装饰' ); } } @f() @g() class Demo { } |
上例的执行顺序为
f求值
g求值
g装饰
f装饰
因为先求值,所以在上面的f会比g先求值。因为装饰器是由下到上装饰,所以求值后的g比f先执行。
装饰器类型
根据被装饰的对象不同,装饰器分为以下几类:
- 类装饰器
- 方法装饰器
- 属性装饰器
- 函数参数装饰器
类装饰器
类装饰器在定义类的地方。类装饰器可以监视、修改或替换类定义。类的构造函数将作为唯一参数传递给装饰器。如果类装饰器返回一个值,它会使用返回的构造函数替换原来的类声明。
1 2 3 4 5 6 7 | function sealed (target: Function) { Object.seal(target); Object.seal(target.prototype); } @ sealed class Demo {} |
下面来一个替换构造函数的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function replace<T extends { new (...args: any[]):{}}>(target: T) { return class extends target { newname = "newName" ; age = 18 } } @replace class Demo { oldname = "oldname" ; constructor(oldname: string ) { this .oldname = oldname; } } console.log( new Demo( "oldname" )); |
以上例程会输出
class_1 { oldname: 'oldname', newname: 'newName', age: 18 }
可以看到通过装饰器新增的newname和age属性已经成功注入了。
方法装饰器
方法装饰器用来装饰类的方法(静态方法和实例方法都可以)。方法装饰器可以监视、修改或替换方法定义。
方法装饰器接收3个参数:
- 类的原型对象,如果是静态方法则为类的构造函数
- 方法名称
- 方法的属性描述符
下面是一个修改
方法行为的装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function hack(target: any, propertyKey: string , descriptor: PropertyDescriptor) { const oldFunction = target[propertyKey]; // 获取方法引用 const newFunction = function(...args: any[]) { console.log( 'call function ' , propertyKey); oldFunction.call(target, ...args); } descriptor.value = newFunction; // 替换原声明 } class Demo { @hack demo() { console.log( 'call demo' ); } } const demo = new Demo(); demo.demo(); |
以上例程输出如下:
call function demo
call demo
属性装饰器
属性装饰器用来装饰类的成员属性。属性装饰器接收两个参数:
- 类的原型对象,如果是静态方法则为类的构造函数
- 属性名
1 2 3 4 5 6 7 8 9 10 11 | function demo(value: string ) { return function(target: any, propertyKey: string ) { target[propertyKey] = value; } } class Demo { @demo( 'haha' ) name?: string ; } const d = new Demo(); console.log(d.name); |
属性装饰器多用在属性依赖注入上面
函数参数装饰器
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 参数的名字。
- 参数在函数参数列表中的索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 | function PathParam(paramDesc: string ) { return function (target: any, paramName: string , paramIndex: number) { !target.$meta && (target.$meta = {}); target.$meta[paramIndex] = paramDesc; } } class Demo { constructor() { } getUser( @PathParam( "userId" ) userId: string ) { } } console.log((<any>Demo).prototype.$meta); |
以上例程输出
{ '0': 'userId' }
函数参数装饰器可以用在开发Web框架时自动注入请求参数。
结语
装饰器的介绍到这里就暂时结束了,装饰器的存在让Typescript有了与Java和C#等语言的注解相同的功能。当然,基于装饰器能做的工作是相当多的,注明的Angular2就大量使用了装饰器来分离业务逻辑。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗