资讯专栏INFORMATION COLUMN

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

Shisui / 400人阅读

摘要:值得注意的是,的返回值复写了原始的构造函数,原因是类装饰器必须返回一个构造器函数。原始构造函数的原型被复制给的原型,以确保在创建一个的新实例时,操作符如愿以偿,具体原因可参考鄙人另一篇文章原型与对象。

上一篇文章中,我们讨论了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 = (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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101520.html

相关文章

  • 装饰与元数据反射(3)参数装饰

    摘要:可见参数装饰器函数需要个参数被装饰类的原型,装饰参数所属的方法名,参数的索引。参数装饰器不应当用来修改构造器方法或属性的行为,它只应当用来产生某种元数据。一旦元数据被创建,我们便可以用其它的装饰器去读取它。 之前已经分别介绍了方法装饰器、属性装饰器和类装饰器,这篇文章我们来继续关注这些话题: 参数装饰器 装饰器工厂 我们将围绕以下这个例子,来探讨这些概念: class Person...

    Barry_Ng 评论0 收藏0
  • 装饰与元数据反射(1)方法装饰

    摘要:使用装饰器的方法很简单在装饰器名前加字符,写在想要装饰的方法上,类似写注释的方式装饰器实际上是一个函数,入参为所装饰的方法,返回值为装饰后的方法。经过装饰过的方法,它依然按照原来的方式执行,只是额外执行了附件的装饰器函数的功能。 让我来深入地了解一下TypeScript对于装饰器模式的实现,以及反射与依赖注入等相关特性。 在Typescript的源代码中,可以看到装饰器能用来修饰cla...

    xiaochao 评论0 收藏0
  • 装饰与元数据反射(4)元数据反射

    摘要:庆幸的是,已经支持反射机制,来看看这个特性吧元数据反射可以通过安装包来使用元数据反射的若要使用它,我们需要在中设置为,同时添加的引用,同时加载文件。复杂类型序列化的团队为复杂类型的元数据序列化做出了努力。 本篇内容包括如下部分: 为什么JavaScript中需要反射 元数据反射API 基本类型序列化 复杂类型序列化 为什么JavaScript中需要反射? 关于反射的概念,摘自百度百...

    gaosboy 评论0 收藏0
  • 聊聊Typescript中的设计模式——装饰篇(decorators)

    摘要:本文从装饰模式出发,聊聊中的装饰器和注解。该函数的函数名。不提供元数据的支持。中的元数据操作可以通过包来实现对于元数据的操作。   随着Typescript的普及,在KOA2和nestjs等nodejs框架中经常看到类似于java spring中注解的写法。本文从装饰模式出发,聊聊Typescipt中的装饰器和注解。 什么是装饰者模式 Typescript中的装饰器 Typescr...

    yiliang 评论0 收藏0
  • 流畅的 Python - 5. 装饰闭包

    摘要:看了这一章,发现原来是装饰器,又一新知识。期间,装饰器会做一些额外的工作。书中介绍了模块中的三个装饰器。另一个是,这个装饰器把函数结果保存了起来,避免传入相同参数时重复计算。叠放不奇怪,装饰器返回的就是函数或可调用对象。 在 Web 框架 Flask 中,最常看到的或许是以@app.route开头的那行代码。由于还是刚接触 Flask,所以对这种语法还不熟悉。看了这一章,发现原来是装饰...

    Markxu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<