03.TypeScript 高级语法

03.TypeScript 高级语法TypeScript 高级语法 1 类的装饰器 装饰器入门 装饰器本身是一个函数 装饰器通过 进行调用 要使用装饰器 tsconfig 需要添加允许装饰器的配置 experimental true

大家好,我是讯享网,很高兴认识大家。

TypeScript 高级语法

1. 类的装饰器

装饰器入门

装饰器本身是一个函数,装饰器通过 @ 进行调用。

要使用装饰器,tsconfig 需要添加允许装饰器的配置:

 "experimentalDecorators": true, "emitDecoratorMetadata": true, 

讯享网

尝试运行以下代码:

讯享网function testDecorator(constructor: any) { 
    console.log("decorator"); } @testDecorator class Test { 
   } 

可以看到控制台输出了 “decorator”。类装饰器在类创建好后立即执行,和是否创建类实例无关。

类装饰器接收的函数是被修饰的类的构造函数。

当使用多个装饰器时候,离 class 越近的,就优先执行。

function testDecorator(constructor: any) { 
    console.log("decorator"); } function testDecorator1(constructor: any) { 
    console.log("decorator1"); } @testDecorator @testDecorator1 class Test { 
   } // 先输出 decorator1 再输出 decorator 
工厂模式生成装饰器

执行一个函数来生成一个新的装饰器。

讯享网function testDecorator(text: string) { 
    return function(constructor: any) { 
    console.log(text); } } @testDecorator("hello") class Test { 
   } 
使用装饰器扩展类的功能

直接放代码:

function testDecorator<T extends new (...args: any[]) => { 
   }>(constructor: T) { 
    return class extends constructor { 
    name = "decorator"; }; } @testDecorator class Test { 
    name: string; constructor(name: string) { 
    console.log(1); this.name = name; console.log(this.name); console.log(2); } } const test = new Test("sjh"); console.log(test); 

输出结果:

讯享网1 sjh 2 Test { name: 'decorator' } 

输出结果表明了,装饰器修改了类中的成员变量,同时,装饰器的执行是在类的构造器之后的。

装饰器上的泛型比较难理解,说白一点,就是指明 T 为具有构造函数的类型。下面详细解析内容:

new (...args: any[]) => { 
   } 

该构造函数函数返回一个对象类型,这个函数能接受任意多的参数,并且不限制类型。

讯享网T extends new (...args: any[] 

T 这种类型可以通过这种构造函数被实例化出来。

装饰器扩展类的方法

按照上面的写法,是这样写的:

function testDecorator<T extends new (...args: any[]) => { 
   }>(constructor: T) { 
    return class extends constructor { 
    name = "decorator"; getName() { 
    return this.name; } }; } @testDecorator class Test { 
    name: string; constructor(name: string) { 
    this.name = name; } } const test = new Test("sjh"); 

但是如果使用 @ 装饰器,TS 是无法提供拓展方法 getName 的提示的,因此得写成较为晦涩的写法:

讯享网function testDecorator<T extends new (...args: any[]) => { 
   }>(constructor: T) { 
    return class extends constructor { 
    name = "decorator"; getName() { 
    return this.name; } }; } const Test = testDecorator( class { 
    name: string; constructor(name: string) { 
    this.name = name; } } ); const test = new Test("sjh"); console.log(test.getName()); 

@ 装饰器直接写成更原生的方法,Test 就是经过修饰后的新的类。这样的话就能被识别,就可以获得提示了。

2. 方法装饰器

同样的,方法也有装饰器。

一般有的场景是,不允许原本的方法被修改,因此可以用方法装饰器进行改装来避免这个问题。还有的场景是,在原本方法的基础上添加新的特性。

方法装饰器本质也是函数,里边有三个参数:


讯享网

function decorator(target: any, key: string, descriptor: PropertyDescriptor) { 
    // 函数执行内容 } 
  • target:如果是普通方法,target 对应的是类的 prototype;如果是静态方法,target 对应的是类的构造函数
  • key:方法名
  • descriptor:存着一些属性,用来控制该被装饰的函数

2.1 应用场景

  1. 类里的方法不可被修改
    讯享网function uneditable(target: any, key: string, descriptor: PropertyDescriptor) { 
          // 该方法不可被重写 descriptor.writable = false; } class Test { 
          name: string; constructor(name: string) { 
          this.name = name; } @uneditable getName() { 
          return this.name; } } const test = new Test("sjh"); test.getName = () => { 
          return "123"; }; // 尝试修改不可被重写的方法,因此会报错 console.log(test.getName()); 
  2. 用方法装饰器修改方法,用 descriptor.value 顶替掉原来的方法。
    function changeFunc(target: any, key: string, descriptor: PropertyDescriptor) { 
          // descriptor.value = function getName descriptor.value = function () { 
          return "modified"; }; } class Test { 
          name: string; constructor(name: string) { 
          this.name = name; } @changeFunc getName() { 
          return this.name; } } const test = new Test("sjh"); console.log(test.getName()); // 输出 modified 

3. 访问器的装饰器

复习一下访问器
讯享网class Test { 
    private _name: string; constructor(name: string) { 
    this._name = name; } get name() { 
    return this._name; } set name(name: string) { 
    this._name = name; } } const test = new Test("sjh"); test.name = "123"; console.log(test.name); // 输出 "123" 
访问器修饰器的使用

访问器本质上还是方法,因此该咋用还是咋用。

例如,数据不可被修改:

function visitDecorator(target: any, key: string, descriptor: PropertyDescriptor) { 
    descriptor.writable = false; } class Test { 
    private _name: string; constructor(name: string) { 
    this._name = name; } get name() { 
    return this._name; } @visitDecorator set name(name: string) { 
    this._name = name; } } const test = new Test("sjh"); test.name = "123"; // setter 上的装饰器不允许修改内容,因此会报错 console.log(test.name); 

4. 属性的装饰器

属性同样有装饰器,但是就不能接收到 descriptor 了。

第一个参数是 target,代表类的原型,第二个参数是 key,为属性名称。

自己建一个 descriptor

虽然没有 descriptor,但是可以自己写。例如,实现属性不可被改写。装饰器函数返回一个 descriptor,里边的 writable 改为 false,可以创造出不能被修改的属性。

讯享网function nameDecorator(target: any, key: string): any { 
    const descriptor: PropertyDescriptor = { 
    writable: false, }; return descriptor; } class Test { 
    @nameDecorator name = "sjh"; } const test = new Test(); test.name = "123"; // 报错 console.log(test.name); 
target 指的是类的原型
function nameDecorator(target: any, key: string): any { 
    // Test.prototype.name = 123 target[key] = "123"; } class Test { 
    @nameDecorator name = "sjh"; } const test = new Test(); console.log(test.name); 

上面的代码输出结果仍然是 “sjh”,原因在于,“sjh” 是在实例下的属性,而装饰器里的 “123” 被放置在了原型上。根据原型链的查找原则,优先找到实例下的属性。

如果要访问 “123”,则需要找实例的隐式原型上的 name 属性。

讯享网console.log((test as any).__proto__.name) 

5. 参数装饰器

可以对类里的方法的参数进行装饰。

装饰器携带三个参数:

  1. target:类的原型
  2. method:方法名
  3. paramIndex:参数在方法里的 index
function paramDecorator(target: any, key: string, paramIndex: number): any { 
    console.log(target, key, paramIndex); } class Test { 
    getInfo(name: string, @paramDecorator age: number) { 
    console.log(name, age); } } const test = new Test(); console.log(test.getInfo("sjh", 18)); 

6. 装饰器的实际使用范例

获取对象里的属性,但是这个属性不一定存在,不存在的话给提示,普通写法会这样写:

讯享网class Test { 
    userInfo: any = undefined; getName() { 
    try { 
    return this.userInfo.name; } catch (e) { 
    console.log("userInfo.name 不存在") } } getAge() { 
    try { 
    return this.userInfo.age; } catch (e) { 
    console.log("userInfo.age 不存在") } } } 

但是这样会有大量的重复代码,因此这里使用装饰器解决。

function catchError(msg: string) { 
    return function(target: any, key: string, descriptor: PropertyDescriptor) { 
    const fn = descriptor.value; descriptor.value = function () { 
    try { 
    fn(); } catch (e) { 
    console.log(msg); } }; } } class Test { 
    userInfo: any = undefined; @catchError("userInfo.name 不存在") getName() { 
    return this.userInfo.name; } @catchError("userInfo.age 不存在") getAge() { 
    return this.userInfo.age; } } 

7. reflect-metadata

元数据是挂在对象上的数据,但是不能直接通过输出查看到。

讯享网yarn add reflect-metadata 
添加和获取元数据内容基本使用
import "reflect-metadata"; const user = { 
    name: "sjh", }; Reflect.defineMetadata("data", "test", user); console.log(Reflect.getMetadata("data", user)); 
用装饰器添加和获取元数据

元数据可以添加到类、类方法、类属性上。

讯享网import "reflect-metadata"; @Reflect.metadata("data", "test") class User { 
    @Reflect.metadata("nameMeta", "hhh") name = "sjh"; } console.log(Reflect.getMetadata("data", User)); // 属性的元数据放在类的原型的对应属性上,方法同理 console.log(Reflect.getMetadata("nameMeta", User.prototype, "name")); 
其他 API
hasMetadata // 有该元数据 hasOwnMetadata // 有该元数据且不是继承过来的 getMetadataKeys // 显示所有的元数据名称 deleteMetadata // 删除元数据 
@Reflect.metadata 实现原理

本质上是一个函数,返回一个装饰器。

自己实现一个功能相同的注解:

讯享网function setData(dataKey: string, msg: string) { 
    return function (target: User, key: string) { 
    Reflect.defineMetadata(dataKey, msg, target, key); }; } @showData class User { 
    @Reflect.metadata("data", "name") getName() { 
    console.log("name"); } @setData("data", "age") getAge() { 
    console.log("age"); } } 

8. 装饰器执行顺序

方法装饰器优先于类装饰器

小讯
上一篇 2025-02-17 15:05
下一篇 2025-03-25 11:17

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/128352.html