03.TypeScript 高级语法

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. 装饰器执行顺序

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值