稀土掘金 稀土掘金

TS系列篇|装饰器(@)

"不畏惧,不将就,未来的日子好好努力"——大家好!我是小芝麻😄

装饰器是一种特殊类型的声明,它能够被附加到类声明方法属性或者参数上,

  • 语法:装饰器使用 @expression 这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入
  • 若要启用实验性的装饰器特性,必须tsconfig.json里启用experimentalDecorators编译器选项
  • 常见的装饰器有: 类装饰器属性装饰器方法装饰器参数装饰器
  • 装饰器的写法: 分为普通装饰器(无法传参)装饰器工厂(可以传参)

1、装饰器的写法

1.1 普通装饰器

interface Person {
  name: string
  age: string
}
function enhancer(target: any) {
  target.prototype.name = '金色小芝麻'
  target.prototype.age = '18'
}
@enhancer // 普通装饰器
class Person {
  constructor() { }
}

1.2 装饰器工厂

interface Person {
    name: string
    age: number
}
// 利用函数柯里化解决传参问题, 向装饰器传入一些参数,也可以叫 参数注解
function enhancer(name: string) {
    return function enhancer(target: any) {
      // 这个 name 就是装饰器的元数据,外界传递进来的参数
      target.prototype.name = name
      target.prototype.age = 18
    }
}
@enhancer('小芝麻') // 在使用装饰器的时候, 为其指定元数据
class Person {
    constructor() {}
}

2、装饰器的分类

2.1 类装饰器

类装饰器在类声明之前声明(紧靠着类声明),用来监视修改或者替换类定义

  • 类装饰器不能用在声明文件中( .d.ts),也不能用在任何外部上下文中(比如declare的类)。
  • 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
  • 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
interface Person {
  name: string
  age: string
}
function enhancer(target: any) {
  target.xx = 'Person' ; // 给类增加属性
  target.prototype.name = '金色小芝麻'
  target.prototype.age = '18'
}
@enhancer // 名字随便起
class Person {
  constructor() { }
}
let p = new Person()
console.log(Person.name); // Person
console.log(p.age) // 18

2.2 属性装饰器

  • 属性装饰器用来装饰属性
  • 属性装饰器表达式会在运行时当做函数被调用,传入下列两个参数
    • 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数: 是属性的名称
function enhancer(target: any, propertyKey: string) {
  console.log(target); // Person {}
  console.log("key " + propertyKey); // key name
};
class Person {
  @enhancer
  name: string;
  constructor() {
    this.name = '金色小芝麻';
  }
}
const user = new Person();
user.name = '你好啊!'
console.log(user.name) // 你好啊!

2.3 方法装饰器

  • 方法装饰器用来装饰方法
    • 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数: 是方法的名称
    • 第三个参数: 是方法的描述 修饰方法
function enhancer(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  // target 如果装饰的是个普通属性的话,那么这个 target 指向类的原型 Person.prototype
 
  console.log(target); // Person { getName: [Function] }
  console.log("key " + propertyKey); // key getName
  console.log("desc " + JSON.stringify(descriptor)); // {"writable":true,"enumerable":true,"configurable":true}
};
class Person {
  name: string;
  constructor() {
    this.name = '金色小芝麻';
  }
  @enhancer
  getName() {
    return 'getName';
  }
}
const user = new Person();
user.getName = function () {
  return '金色小芝麻'
}
console.log(user.getName()); // '金色小芝麻'

修饰静态方法

// 声明装饰器修饰静态方法
function enhancer(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  // target 装饰的是一个类的属性static,那么这个 target 指向类的定义
  
  console.log(target); // [Function: Person] { getAge: [Function] }
  console.log("key " + propertyKey); // key getAge
  console.log("desc " + JSON.stringify(descriptor)); // {"writable":true,"enumerable":true,"configurable":true}
};
class Person {
  age: number = 18;
  constructor() {}
  @enhancer
  static getAge() {
    return 'static getAge';
  }
}
const user = new Person();
Person.getAge = function () {
  return '你好啊!'
}
console.log(Person.getAge()) // 你好啊!

2.4 参数装饰器

  • 参数装饰器用来装饰参数
    • 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数: 成员的名字
    • 第三个参数: 参数在函数参数列表中的索引
function enhancer(target: any, propertyKey: string, parameterIndex: number) {
  console.log(target); // Person { getName: [Function] }
  console.log("key " + propertyKey); // key getName
  console.log("index " + parameterIndex); // index 0
};
class Person {
  name: string;
  constructor() {
    this.name = '你好啊!';
  }
  getName(@enhancer name: string){
    return name
  }
}
const user = new Person();
user.name = '金色小芝麻'
console.log(user.name) // '金色小芝麻'

2.5 装饰器执行顺序

  • 属性方法先执行,谁先写 先执行谁
  • 方法的时候, 先参数在方法,而且一定会在一起
  • 最后是类
  • 如果同类型,先执行离类近的

3、装饰器的原理

我们以下列 类装饰器 为例:

interface Person {
  name: string
  age: string
}
function enhancer(target: any) {
  target.xx = 'Person' ;
  target.prototype.name = '金色小芝麻'
  target.prototype.age = '18'
}
@enhancer // 名字随便起
class Person {
  constructor() { }
}
let p = new Person()

编译结果为:

image.png

我们先把装饰器的实现这部分代码拿出来稍微整理一下代码如下:

var __decorate =
  // 当前上下文是否有 __decorate 这个函数,如果有就返回这个函数,如果没有就定义一个
  //( this && this.__decorate 的作用 :为了避免重复声明)
  (this && this.__decorate) || function (decorators, target, key, desc) {
    /**
     * decorators: 装饰器数组, 
     * target: 构造函数, 
     * key: 方法名称,
     * desc: 方法描述
     */
    var c = arguments.length, // c: 当前参数的个数,此例中我们只传了两个参数,所以 c = 2 
        r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, // c < 3 所以 r = target(构造函数)
        d;
    // Reflect 详解:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
    // 如果系统支持反射,则直接使用Reflect.decorate(decorators,target, key, desc)方法。
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") { 
      r = Reflect.decorate(decorators, target, key, desc);
    } else {// 否则自行定义实现装饰器机制的代码。
      for (var i = decorators.length - 1; i >= 0; i--) {
        if (d = decorators[i]) { // 给 d 赋值为 装饰器数组中的每一项 并且 不为 undefined 
          // c < 3 执行 d(r) 
          r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        }
      }
    }
    return c > 3 && r && Object.defineProperty(target, key, r), r;
    // return c > 3 && r && Object.defineProperty(target, key, r), r; 
    // 等价于 =>
    // if(c > 3 && r) { 
    //    Object.defineProperty(target, key, r) 
    // }
    // return r
  };
var __metadata = (this && this.__metadata) || function (k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
function enhancer (target) {
  // target 接收的实参是: r (是 构造函数 Person)
  target.xx = 'Person'; // 给 Person 增加属性 
  target.prototype.name = '金色小芝麻'; // 给 Person 的原型 增加 name 属性 
  target.prototype.age = '18'; // 给 Person 的原型 增加 age 属性 
}
let Person = class Person {
  constructor() { }
};
Person = __decorate(
  // 传入两个实参,1、当前类使用的装饰器数组, 2、当前构造函数
  [enhancer, __metadata("design:paramtypes", []) ], Person
);
let p = new Person();

2.1 执行步骤

  • 1、定义一个 __decorate 函数(实现装饰器)
  • 2、元数据我们暂时不考虑
  • 3、定义一个 enhancer 函数
  • 4、定义一个 Person 的 构造函数
  • 5、给 Person 重新赋值
    • 5.1 执行: __decorate([ enhancer, __metadata("design:paramtypes", [])], Person);

image.png

  • 链接: Reflect 、 Object.getOwnPropertyDescriptor()

参考文献

[1]. TypeScript中文网

[2]. TypeScript 入门教程

装修网惠州简装全包案例精装修二手房 装修永州装修报价100平方房子简装需要多少钱2020流行什么风格的装修兰州10大装修公司装修装饰嘉兴威海精装房厨房装修设计公司装修装饰口碑店铺的装修格局79平米简装多少钱简装的好处成都100平米装修多少钱大学的装修专业1跃层装修300平米酒吧装修多少钱简装的二手房怎么装修密云哪家装修公司好装修幼稚园设计老年人装修通辽市装修公司南阳市装修装饰省钱办公室装修美式风格简约装修衡阳正规装修公司北京市内的装修公司湛江装修公司哪家好装修公司公装怎么样三室简装香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声汪小菲曝离婚始末卫健委通报少年有偿捐血浆16次猝死单亲妈妈陷入热恋 14岁儿子报警雅江山火三名扑火人员牺牲系谣言手机成瘾是影响睡眠质量重要因素男子被猫抓伤后确诊“猫抓病”中国拥有亿元资产的家庭达13.3万户高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了男孩8年未见母亲被告知被遗忘张家界的山上“长”满了韩国人?倪萍分享减重40斤方法许家印被限制高消费网友洛杉矶偶遇贾玲何赛飞追着代拍打小米汽车超级工厂正式揭幕男子被流浪猫绊倒 投喂者赔24万沉迷短剧的人就像掉进了杀猪盘特朗普无法缴纳4.54亿美元罚金周杰伦一审败诉网易杨倩无缘巴黎奥运专访95后高颜值猪保姆德国打算提及普京时仅用姓名西双版纳热带植物园回应蜉蝣大爆发七年后宇文玥被薅头发捞上岸房客欠租失踪 房东直发愁“重生之我在北大当嫡校长”校方回应护栏损坏小学生课间坠楼当地回应沈阳致3死车祸车主疑毒驾事业单位女子向同事水杯投不明物质路边卖淀粉肠阿姨主动出示声明书黑马情侣提车了奥巴马现身唐宁街 黑色着装引猜测老人退休金被冒领16年 金额超20万张立群任西安交通大学校长王树国卸任西安交大校长 师生送别西藏招商引资投资者子女可当地高考胖东来员工每周单休无小长假兔狲“狲大娘”因病死亡外国人感慨凌晨的中国很安全恒大被罚41.75亿到底怎么缴考生莫言也上北大硕士复试名单了专家建议不必谈骨泥色变“开封王婆”爆火:促成四五十对测试车高速逃费 小米:已补缴天水麻辣烫把捣辣椒大爷累坏了

装修网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化