前言

相较于类装饰器,成员装饰器更加常用一些。

关键词
成员装饰器,PropertyDescriptor

参考资料

  • Object.defineProperty:链接

    notes

属性

属性装饰器也是一个函数,该函数需要两个参数:
参数1:如果是静态属性,则为类本身;如果是实例属性,则为类的原型
参数2:固定为一个字符串,表示属性名

方法

方法装饰器也是一个函数,该函数需要三个参数:
参数1:如果是静态方法,则为类本身;如果是实例方法,则为类的原型;
参数2:固定为一个字符串,表示方法名;
参数3:属性描述对象(PropertyDescriptor);

使用 PropertyDescriptor 来约束属性描述符对象。

无论是「属性」还是「方法」,都支持多个装饰器。

codes

  1. function d(target: any, key: string) {
  2. console.log(key, target, target === A.prototype, target === A);
  3. }
  4. class A {
  5. @d
  6. prop1: string;
  7. @d
  8. prop2: number;
  9. @d
  10. static prop3: string;
  11. }

image.png

  1. function d(target: any, key: string) {
  2. if (!target.__props) target.__props = [];
  3. target.__props.push(key);
  4. }
  5. class A {
  6. @d
  7. prop1: string;
  8. @d
  9. prop2: number;
  10. @d
  11. static prop3: string;
  12. }
  13. console.log((A.prototype as any).__props); // [ 'prop1', 'prop2' ]
  1. class A {}
  2. const a = new A();
  3. Object.defineProperty(a, "abc", {
  4. writable: false,
  5. value: "123",
  6. enumerable: false,
  7. })
  8. /*
  9. 属性描述符对象:
  10. {
  11. writable: false,
  12. value: "123",
  13. enumerable: false,
  14. }
  15. */

这个属性描述符对象描述的是 a.abc 的相关信息:

  • writable:是否能够被赋值
  • value:属性值
  • enumerable:是否可被枚举
  1. export {};
  2. function d() {
  3. return function (target: any, key: string, descriptor: PropertyDescriptor) {
  4. console.log(key, target === A.prototype, target === A, descriptor);
  5. };
  6. }
  7. class A {
  8. @d()
  9. method1() {}
  10. @d()
  11. static method2() {}
  12. }
  13. const a = new A();
  14. for (const key in a) {
  15. console.log(key);
  16. }

image.png

从打印的属性描述符对象可以看出,方法是不可以被枚举的,我们可以改写属性描述符对象身上的 enumerable 属性,令实例方法可以被枚举。

  1. export {};
  2. function d() {
  3. return function (target: any, key: string, descriptor: PropertyDescriptor) {
  4. descriptor.enumerable = true;
  5. };
  6. }
  7. class A {
  8. @d()
  9. method1() {}
  10. @d()
  11. static method2() {}
  12. @d()
  13. method3() {}
  14. }
  15. const a = new A();
  16. for (const key in a) {
  17. console.log(key); // => method1 method3
  18. }

枚举实例身上的所有属性。

注意,被 static 修饰符修饰的方法 method2 不是实例方法,所以无法被枚举。
通过 a.method2 访问是错误的
通过 A.method2 访问是正确的

  1. export {};
  2. function enumerable(target: any, key: string, descriptor: PropertyDescriptor) {
  3. descriptor.enumerable = true;
  4. };
  5. class A {
  6. @enumerable
  7. method1() {}
  8. static method2() {}
  9. @enumerable
  10. method3() {}
  11. }
  12. const a = new A();
  13. for (const key in a) {
  14. console.log(key); // => method1 method3
  15. }

@enumerable 单独提取出来
将其单独提取出来,想让哪个实例方法能够被遍历,就往哪个方法上边加 @enumerable 即可,使用起来会更加灵活。

  1. export {};
  2. function enumerable(target: any, key: string, descriptor: PropertyDescriptor) {
  3. descriptor.enumerable = true;
  4. }
  5. function useless(target: any, key: string, descriptor: PropertyDescriptor) {
  6. descriptor.value = function () {
  7. console.log(`${key}方法已过期`);
  8. };
  9. }
  10. class A {
  11. @enumerable
  12. method1() {
  13. console.log('method1 执行了');
  14. }
  15. static method2() {}
  16. @enumerable
  17. @useless
  18. method3() {
  19. console.log('method3 执行了');
  20. }
  21. }
  22. const a = new A();
  23. for (const key in a) {
  24. console.log(key); // => method1 method3
  25. (a as any)[key]();
  26. }

image.png

如果一个方法不再使用了,只要添加一个 useless 装饰器即可。当我们和同事共同开发的时候,这种做法也能够帮我们降低不小的开发、沟通成本。