TypeScript的装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为,通俗来讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能

装饰器已经是ES7的标准特性之一

常见的装饰器

  • 类装饰器
  • 属性装饰器
  • 方法装饰器
  • 参数装饰器

装饰器的写法

  • 普通装饰器(无法传参)
  • 装饰器工厂(可传参)

因为装饰器只是个未来期待的用法,所以默认是不支持的,如果想要使用就要打开tsconfig.json中的experimentalDecorators,否则会报语法错误


1. 类装饰器

类装饰器在类声明之前被声明(紧跟着类声明),类装饰器应用于类构造函数,可以用来监视,修改或替换类定义,需要传入一个参数

1.1 普通装饰器

function logClass(target: any) {
  console.log(target);
  // target就是当前类,在声明装饰器的时候会被默认传入
  target.prototype.apiUrl = "动态扩展的属性";
  target.prototype.run = function() {
    console.log("动态扩展的方法");
  };
}

@logClass
class HttpClient {
  constructor() {}
  getData() {}
}
// 这里必须要设置any,因为是装饰器动态加载的属性,所以在外部校验的时候并没有apiUrl属性和run方法
let http: any = new HttpClient();
console.log(http.apiUrl);
http.run();

1.2 装饰器工厂

如果要定制一个修饰器如何应用到一个声明上,需要写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用

function color(value: string) { // 这是一个装饰器工厂
  return function (target: any) { // 这是装饰器,这个装饰器就是上面普通装饰器默认传入的类
    // do something with "target" and "value"...
  }
}
function logClass(value: string) {
  return function(target: any) {
    console.log(target);
    console.log(value);
    target.prototype.apiUrl = value; // 将传入的参数进行赋值
  };
}

@logClass("hello world") // 可传参数的装饰器
class HttpClient {
  constructor() {}
  getData() {}
}

let http: any = new HttpClient();
console.log(http.apiUrl);

1.3 类装饰器重构构造函数

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数,如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明

/*
    通过返回一个继承的类实现一个类的属性和方法的重构,换句话说就是在中间层有一个阻拦,然后返回的
是一个新的继承了父类的类,这个类必须有父类的所有属性和方法,不然会报错
*/
function logClass(target: any) {
  return class extends target { // 可以当做是固定写法
    apiUrl: string = "我是修改后的数据";
    getData() {
      console.log(this.apiUrl);
    }
  };
}

// 重构属性和方法
@logClass
class HttpClient {
  constructor(public apiUrl = "我是构造函数中的数据") {}
  getData() {
    console.log(123);
  }
}

let http: any = new HttpClient();
console.log(http.apiUrl); // 我是修改后的数据
http.getData(); // 我是修改后的数据


2. 属性装饰器

属性装饰器表达式在运行时当作函数被调用,传入两个参数(都是自动传入的):

  • 对应静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  • 成员的名字
function logProperty(value: string) {
  return function(target: any, attr: string) {
    // target为实例化的成员对象,attr为下面紧挨着的属性
    console.log(target);
    console.log(attr);
    target[attr] = value; // 可以通过修饰器改变属性的值
  };
}

class HttpClient {
  @logProperty("hello world") // 修饰器后面紧跟着对应要修饰的属性
  public url: string | undefined;
  constructor() {}
  getData() {
    console.log(this.url);
  }
}

let http: any = new HttpClient();
http.getData(); // hello world

3. 方法装饰器

方法装饰器被应用到方法的属性描述符上,可以用来监视,修改或替换方法定义,传入三个参数(都是自动传入的):

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 成员的名字(只是个string类型的字符串,没有其余作用)
  • 成员的属性描述符是一个对象,里面有真正的方法本身
function get(value: any) {
  return function(target: any, methodName: any, desc: any) {
    console.log(target); // HttpClient类
    console.log(methodName); // getData方法名,一个字符串
    console.log(desc); // 描述符
    console.log(desc.value); // 方法本身就在desc.value中
    target.url = 123; // 也能改变原实例
  };
}

class HttpClient {
  public url: any | undefined;
  constructor() {}
  @get("hello world")
  getData() {
    console.log(this.url);
  }
}

let http = new HttpClient();
console.log(http.url); // 123
function get(value: any) {
  return function(target: any, methodName: any, desc: any) {
    let oMethod = desc.value;
    desc.value = function(...args: any[]) {
    // 因为用了方法装饰器,所以实际调用getData()方法的时候会调用desc.value来实现,通过赋值可以实现重构方法
    // 原来的方法已经赋值给oMethod了,所以可以改变
      args = args.map( // 这段代码是将传入的参数全部转换为字符串
        (value: any): string => {
          return String(value);
        }
      );
      console.log(args); // 因为方法重构了,所以原来的getData()中的代码无效了,调用时会打印转换后参数
      /*
        如果想依然能用原来的方法,那么写入下面的代码,相当于就是对原来的方法进行了扩展
      */
       oMethod.apply(target, args); // 通过这种方法调用可以也实现原来的getData方法
    };
  };
}

class HttpClient {
  public url: any | undefined;
  constructor() {}
  @get("hello world")
  getData(...args: any[]) {
    console.log(args); // [ '1', '2', '3', '4', '5', '6' ]
    console.log("我是getData中的方法");
  }
}

let http = new HttpClient();
http.getData(1, 2, 3, 4, 5, 6); // [ '1', '2', '3', '4', '5', '6' ]

4. 方法参数装饰器

参数装饰器被表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入三个参数(都是自动传入的):

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 方法的名字(只是个string类型的字符串,没有其余作用)
  • 参数在函数参数列表中的索引
// 这个装饰器很少使用
function logParams(value: any) {
  return function(target: any, methodName: any, paramsIndex: any) {
    console.log(target);
    console.log(methodName); // getData
    console.log(paramsIndex); // 1,因为value在下面是第二个参数
  };
}

class HttpClient {
  public url: any | undefined;
  constructor() {}
  getData(index: any, @logParams("hello world") value: any) {
    console.log(index);
    console.log(value);
  }
}

let http: any = new HttpClient();
http.getData(0, "123"); // 我是修改后的数据

5. 装饰器的执行顺序

装饰器的执行顺序大部分按照代码的执行顺序运行

如果有多个同样的装饰器,会从后到前依次执行

如果方法和方法参数装饰器在同一个方法出现,参数装饰器先执行

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,208评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,502评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,496评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,176评论 1 302
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,185评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,630评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,992评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,973评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,510评论 1 325
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,546评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,659评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,250评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,990评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,421评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,569评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,238评论 3 382
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,732评论 2 366