装饰器与元数据反射(2)属与类性装饰器

栏目: JavaScript · 发布时间: 5年前

内容简介:上一篇文章中,我们讨论了TypeScript源码中关于方法装饰器的实现,搞明白了如下几个问题:接下来我们继续属性装饰器的声明标识如下:

上一篇文章中,我们讨论了TypeScript源码中关于方法装饰器的实现,搞明白了如下几个问题:

__decorate

接下来我们继续 属性装饰器 的观察。

属性装饰器

属性装饰器的声明标识如下:

declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;

如下我们为一个类的属性添加了一个名为 @logProperty 的装饰器

class Person { 

  @logProperty
  public name: string;
  public surname: string;

  constructor(name : string, surname : string) { 
    this.name = name;
    this.surname = surname;
  }
}

上一篇解释过,当这段代码最后被编译成JavaScript执行时,方法 __decorate 会被调用,但此处会少最后一个参数(通过 Object. getOwnPropertyDescriptor 属性描述符)

var Person = (function () {
    function Person(name, surname) {
        this.name = name;
        this.surname = surname;
    }
    __decorate(
      [logProperty],
      Person.prototype,
      "name"
    );
    return Person;
})();

需要注意的是,这次TypeScript编译器并没像方法装饰器那样,使用 __decorate 返回的结果覆盖原始属性。原因是属性装饰器并不需要返回什么。

Object.defineProperty(C.prototype, "foo",
    __decorate(
      [log],
      C.prototype,
      "foo",
      Object.getOwnPropertyDescriptor(C.prototype, "foo")
    )
);

那么,接下来具体实现这个 @logProperty 装饰器

function logProperty(target: any, key: string) {

  // 属性值
  var _val = this[key];

  // getter
  var getter = function () {
    console.log(`Get: ${key} => ${_val}`);
    return _val;
  };

  // setter
  var setter = function (newVal) {
    console.log(`Set: ${key} => ${newVal}`);
    _val = newVal;
  };

  // 删除属性
  if (delete this[key]) {
    // 创建新的属性
    Object.defineProperty(target, key, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  }
}

实现过程首先声明了一个变量 _val ,并用所装饰的属性值给它赋值(此处的 this 指向类的原型, key 为属性的名字)。

接着声明了两个方法 gettersetter ,由于函数是闭包创建的,所以在其中可以访问变量 _val ,在其中可以添加额外的自定义行为,这里添加了将属性值打印在控制台的操作。

然后使用 delete 操作符将原属性从类的原型中删除,不过需要注意的是:如果属性存在不可配置的属性时,这里 if(delete this[key]) 会返回false。而当属性被成功删除,方法 Object.defineProperty() 将创建一个和原属性同名的属性,不同的是新的属性 gettersetter 方法,使用上面新创建的。

至此,属性装饰器的实现就完成了,运行结果如下:

var me = new Person("Remo", "Jansen");  
// Set: name => Remo

me.name = "Remo H.";                       
// Set: name => Remo H.

name;
// Get: name Remo H.

类装饰器

类装饰器的声明标识如下:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

可以像如下方式使用类装饰器:

@logClass
class Person { 

  public name: string;
  public surname: string;

  constructor(name : string, surname : string) { 
    this.name = name;
    this.surname = surname;
  }
}

和之前不同的是,经过TypeScript编译器编译为JavaScript后,调用 __decorate 函数时,与方法装饰器相比少了后两个参数。仅传递了 Person 而非 Person.prototype

var Person = (function () {
    function Person(name, surname) {
        this.name = name;
        this.surname = surname;
    }
    Person = __decorate(
      [logClass],
      Person
    );
    return Person;
})();

值得注意的是, __decorate 的返回值复写了原始的构造函数,原因是类装饰器必须返回一个构造器函数。接下来我们就来实现上面用到的类装饰器 @logClass

function logClass(target: any) {

  // 保存对原始构造函数的引用
  var original = target;

  // 用来生成类实例的方法
  function construct(constructor, args) {
    var c : any = function () {
      return constructor.apply(this, args);
    }
    c.prototype = constructor.prototype;
    return new c();
  }

  // 新的构造函数
  var f : any = function (...args) {
    console.log("New: " + original.name); 
    return construct(original, args);
  }

  // 复制原型以便`intanceof`操作符可以使用
  f.prototype = original.prototype;

  // 返回新的构造函数(会覆盖原有构造函数)
  return f;
}

这里实现的构造器中,声明了一个名为 original 的变量,并将所装饰类的构造函数赋值给它。接着声明一个 工具 函数 construct ,用来创建类的实例。然后定义新的构造函数 f ,在其中调用原来的构造函数并将初始化的类名打印在控制台,当然我们也可以添加一些其他自定义的行为。

原始构造函数的原型被复制给 f 的原型,以确保在创建一个 Person 的新实例时, instanceof 操作符如愿以偿,具体原因可参考鄙人另一篇文章原型与对象。

至此类装饰器的实现就完成了,可以验证下:

var me = new Person("Remo", "Jansen");  
// New: Person

me instanceof Person; 
// true

以上所述就是小编给大家介绍的《装饰器与元数据反射(2)属与类性装饰器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

奔跑吧 Linux内核

奔跑吧 Linux内核

张天飞 / 人民邮电出版社 / 2017-9-1 / CNY 158.00

本书内容基于Linux4.x内核,主要选取了Linux内核中比较基本和常用的内存管理、进程管理、并发与同步,以及中断管理这4个内核模块进行讲述。全书共分为6章,依次介绍了ARM体系结构、Linux内存管理、进程调度管理、并发与同步、中断管理、内核调试技巧等内容。本书的每节内容都是一个Linux内核的话题或者技术点,读者可以根据每小节前的问题进行思考,进而围绕问题进行内核源代码的分析。 本书内......一起来看看 《奔跑吧 Linux内核》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具