ts可展开注释_TS语法之装饰器(注解)

本文详细介绍了 TypeScript 中的装饰器(注解)语法,包括方法注解、Getter/Setter注解、类注解、属性注解和参数注解的用法,并提供了多个实际示例。通过装饰器,可以实现对类、方法、属性和参数的元数据操作,增强代码的功能和可读性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面的话

本文只讲解 TypeScript 中的装饰器语法(下称注解), 只会告诉你如何编写一个自定义注解,且通过注解简单的修改逻辑,不涉及 反射 或 元编程 等其他更进一步的代码讲解,如果有兴趣可以自行搜索相关的进阶写法,比如这位:

开始

注1:装饰器仅用于 class 语法中,所以以下举例中只会使用 class 写法。(其实是我只测试了 class 语法中的使用,而且也没必要在普通方法上使用注解)

注2:tsconfig.json 中 target 必须为 ES5 或以上版本。且 experimentalDecorators 与 emitDecoratorMetadata 字段需为 true

由于我们写的是 TypeScript,我们首先定义几个 interface 和 type ,让 IDE 有一些基本提示:

type Prototype = {

constructor: Function

} & any

type Constructor = { new(...args: any[]): {} };

interface FunctionAnnotation {

(target: Prototype, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): void;

}

interface ConstructorAnnotation {

(constructor: T): T;

}

interface PropertyAnnotation {

(target: Prototype, propertyKey: PropertyKey): void;

}

interface ParameterAnnotation {

(target: Prototype, propertyKey: PropertyKey, parameterIndex: number): void;

}

其中,PropertyKey 和 TypedPropertyDescriptor 都在 lib.es5.d.ts 文件中有定义,其值为:

declare type PropertyKey = string | number | symbol;

interface TypedPropertyDescriptor {

enumerable?: boolean;

configurable?: boolean;

writable?: boolean;

value?: T;

get?: () => T;

set?: (value: T) => void;

}

如果有人不喜欢泛型写法,也可以将 TypedPropertyDescriptor 替换成 PropertyDescriptor

interface PropertyDescriptor {

configurable?: boolean;

enumerable?: boolean;

value?: any;

writable?: boolean;

get?(): any;

set?(v: any): void;

}这里使用泛型仅仅是因为我不喜欢 any,Prototype 类型中加上 any 是因为没有办法只能用 any,原因是类型定义中没有办法用 symbol 类型的值定义成 key,否则就可以写成 type Prototype = {[key: string]: Function; [key: number]: Function; [key: symbol]: Function;} 了

如果有人不知道 PropertyDescriptor 是干嘛的,请参考 Object.defineProperty()

下面我们直接用例子教你如何自定义一个注解。

方法注解

写法:

export function logFuncCall(): FunctionAnnotation {

return function (target, propertyKey, descriptor) {

if (typeof descriptor.value === "function") {

let value: Function = descriptor.value;

// @ts-ignore

descriptor.value = function (...args: any) {

console.log(`${Date()} ${target.constructor.name}["${String(propertyKey)}"] be Called`);

return value.call(this, ...args);

};

}

};

}

注:若注解中需要传入参数,参考正常方法的写法添加需要的参数即可,不赘述。(注解与正常方法的区别仅仅在于方法调用前面多了一个@符号而已)

参数详解:target:被注解类的 prototype ,本例中为 Test.prototype,如果被注解的方法是静态方法,则为类本身,也就是 Test

propertyKey:被注解的方法名,本例中为 method

descriptor:属性描述,参考 Object.defineProperty(o, p, attributes)中 attributes 参数

需要注意的是,如果注解中不需要传入任何参数,则可以省略注解中最外层的一层方法,变成如下:

export function logFuncCall(target, propertyKey, descriptor) {

if (typeof descriptor.value === "function") {

let value: Function = descriptor.value;

// @ts-ignore

descriptor.value = function (...args: any) {

console.log(`${target.constructor.name}["${String(propertyKey)}"] be Called`);

return value.call(this, ...args);

};

}

}

这时,注解则可以写成 @logFuncCall 的形式(去掉了括号),否则括号不能省略。下同,不再赘述。本文并未采用省略写法。

直接编译调试运行如下代码:

class Test {

@logFuncCall()

method() {}

}

let t = new Test("ttt");

t.method();

这时,控制台中会输出:

Test["method"] be Called

Getter/Setter注解

同 方法注解;

与方法注解的区别在于,Getter/Setter 注解的 descriptor 中只有对应的 get 和 set 字段,而 value 字段为 undefined,而方法注解只中有 value 字段,而 get 和 set 字段为 undefined

所以通用的方法注解应该如下:

export function logFuncCall(): FunctionAnnotation {

return function (target, propertyKey, descriptor) {

if (typeof descriptor.value === "function") {

let value: Function = descriptor.value;

// @ts-ignore

descriptor.value = function (...args: any) {

console.log(`${target.constructor.name}["${String(propertyKey)}"] be Called`);

return value.call(this, ...args);

};

}

if (typeof descriptor.get === "function") {

let get = descriptor.get;

descriptor.get = function () {

console.log(`${target.constructor.name}["get ${String(propertyKey)}"] be Called`);

return get.call(this);

};

}

if (typeof descriptor.set === "function") {

let set = descriptor.set;

descriptor.set = function (value) {

console.log(`${target.constructor.name}["set ${String(propertyKey)}"] be Called`);

set.call(this, value);

};

}

};

}

类注解

写法:

export function logCreate(): ConstructorAnnotation {

return (constructor: T) => {

return class extends constructor {

constructor(...args: any[]) {

console.log(`${Date()} ${constructor.name} new an instance`);

super(...args);

}

};

};

}

参数详解constructor:被注解类的原对象,而不是类的 prototype

上面这种写法适用于大部分情况,类似于继承了被注解的类

如果你不希望任何人重写此类的方法,可以用如下的匿名写法:(在类的 constructor 方法中返回一个对象,则 new 对象时 this 指向该对象,而非类的实例)

export function logCreate(): ConstructorAnnotation {

return (constructor: T) => {

return class {

constructor(...args: any[]) {

console.log(`${Date()} ${constructor.name} new an instance`);

return new constructor(...args);

}

};

};

}

这种写法需要注意的是:环境中被注解的类将会变为一个注解返回的匿名内部类,其不继承于原先的类,在类外也无法调用原先类的所有方法,只有使用 new 新建对象后,才会返回一个原先类的对象(除非你在内部类中自己定义了静态方法去调用原先类的静态方法)

被这种写法注解的类如果被继承,则只能在构造函数中对自身做出一定修改,且所有静态方法不可通过类名调用

属性注解

写法:

export function logGetSet(): PropertyAnnotation {

return function (target, propertyKey) {

let s = "_" + String(propertyKey);

Object.defineProperty(target, propertyKey, {

get() {

console.log(`get ${String(propertyKey)}`);

return this[s];

},

set(v) {

console.log(`set ${String(propertyKey)} => ${v}`);

this[s] = v;

},

configurable: true,

enumerable: true,

});

};

}

参数详解target:参考方法注解

propertyKey:参考方法注解

注:propertyKey 是有可能为 symbol 或 number 类型的,所以 String() 大概率不能省略

这里需要注意的是:由于注解执行紧跟在类的定义之后,所以如果没有在定义时赋默认值,则 target 中是不会有对应的属性字段的,这时可以选择自己定义 Getter/Setter 然后在其中编写对应的方法,也可以选择在项目中引入 import 'reflect-metadata',使用 反射-元数据 编程(参考本文开头给出的链接 )

如果注解的是静态字段,且在定义时就赋过值,则 target 中将会有对应的属性字段且有值

参数注解

写法:

export function logParam(): ParameterAnnotation {

return function (target, propertyKey, parameterIndex) {

...

};

}

参数详解target:参考方法注解

propertyKey:参考方法注解

parameterIndex:被注解参数位置,从 0 开始

这里除了反射以外好像没别的用法?或者跟方法注解联动,毕竟方法也是对象,是可以保存参数的,案例:

export function logFuncCall(): FunctionAnnotation {

return function (target, propertyKey, descriptor) {

if (typeof descriptor.value === "function") {

let value: Function = descriptor.value;

// @ts-ignore

descriptor.value = function (...args: any) {

// @ts-ignore

value.logParam?.forEach(i => console.log(`第${i}参数: ${args[i]}`));

return value.call(this, ...args);

};

}

}

export function logParam(): ParameterAnnotation {

return function (target, propertyKey, parameterIndex) {

target[propertyKey].logParam = [...target[propertyKey].logParam ?? [], parameterIndex];

};

}

class Test {

@logFuncCall()

set(@logParam() log: string) {}

}

new Test.set("aaa")

> 第0参数: aaa

这样同时注解 @logFuncCall() 和 @logParam 之后,就可以打印传入的参数了

结尾

另外附上一些自己写的小案例:

type Prototype = {

constructor: Function

__proto__: Prototype

} & any

/** (运行时)当父类中没有对应方法时提示报错 */

export function override(): FunctionAnnotation {

return (target, propertyKey) => {

if (target.__proto__?.[propertyKey] === undefined) {

console.error(`Method "${String(propertyKey)}" Not A Override Function`);

}

};

}

/** 自动调用父类的对应方法 */

export function autoCallSuper(): FunctionAnnotation {

return (target, propertyKey, descriptor) => {

if (typeof descriptor.value === "function") {

let value = descriptor.value;

if (typeof target.__proto__?.[propertyKey] === "function") {

// @ts-ignore

descriptor.value = function (...args: any) {

target.__proto__[propertyKey].apply(this, args);

return value.apply(this, args);

};

} else {

console.error(`${target.__proto__.constructor.name} No Function Name is "${String(propertyKey)}"`);

}

} else {

console.warn(`"autoCallSuper" Annotation Only Used On NormalFunction, Not Getter/Setter`);

}

};

}

测试案例:

@logCreate()

class A {

@logFuncCall()

a() {

console.log("A");

}

}

@logCreate()

class B extends A {

constructor() {

super();

this.a();

}

@logFuncCall()

@autoCallSuper()

a() {

console.log("B");

}

static b() {}

}

console.dir(B);

console.dir(new B());

B.b();

调试 log:

[class (anonymous) extends B] // console.dir(B);

B new an instance // new B()

A new an instance // super被调用

B["a"] be Called // 调用a方法

A["a"] be Called // 由于autoCallSuper,先调用super方法

A // super方法打印A

B // 重写方法打印B

B {} // console.dir(new B());打印new出来的对象

Function["b"] be Called // B.b();

如果类注释采用了匿名写法,则 log 为:

[class (anonymous)] // 匿名类没有继承B

B new an instance // new B()

A new an instance // super被调用,但是返回了A对象,导致this指针变为了A对象的实例

A["a"] be Called // this.a()

A // 方法打印A

A {} // console.dir(new B());打印new出来的对象

Uncaught TypeError: B.b is not a function // 匿名类没有继承B,自然也就没有B里的静态方法,报错

最后,小心溢出,不用

最后,性能太低,不用

最后,还没成真的语法呢,不用

最后,编写一时爽,debug火葬场

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值