A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 AreYouGlad 于 2017-12-20 11:53 编辑



装饰器
简介
  • 装饰器的作用主要是用来修饰类行为的, 这就像它的名字一样, 给别人来个锦上添花
  • 这个特性同样来自其他语言, 目前ECMA也有一个提案要把这项功能融入js当中, 未来有很大可能被正式采纳

编译说明
  • 由于装饰器还没有正式成为ECMA标准, 未来可能还会有些许变化
  • 所以要在ts中使用它需要添加experimentalDecorators编译选项
  • 同时还要指明编译后的ES版本大于等于5, 因为装饰器会用到ES5中的特性, 否则转换后的代码会有问题
  • 使用编译命令: tsc 文件名.ts --target ES5 --experimentalDecorators
  • 或者先写好配置文件, 然后直接运行tsc命令, 根据配置文件进行编译

使用说明
  • 目前装饰器只能应用与类/实例与静态成员身上
  • 应用在类上, 装饰器函数可以自动接收一个参数: 类
  • 应用在静态方法上, 装饰器函数可以自动接收三个参数: 类 属性名 描述对象
  • 应用在静态属性上, 装饰器函数可以自动接收两个参数: 类 属性名
  • 应用在实例方法上, 装饰器函数可以自动接收三个参数: 原型 属性名 描述对象
  • 应用在实例属性上, 装饰器函数可以自动接收两个参数: 原型 属性名

初探装饰器

  • 装饰器本质就是一个函数,内部封装了一些用以修饰别人的公共处理逻辑,
  • 装饰器的语法就是在要修饰的东西前面使用@符号调用函数

装饰器语法
[AppleScript] 纯文本查看 复制代码
function decoratorFn(...arg: any[]) {
                console.log('我执行了, 并且我自动收到了下面的东西!');
                console.log(arg);
}

// 写法1: 在类的前一行使用装饰器
@decoratorFn
class Test {}

// 写法2: 在类前面的使用装饰器
@decoratorFn class Test2 {}

装饰属性与方法
[AppleScript] 纯文本查看 复制代码
function decoratorFn(...arg: any[]) {
                console.log('我执行了, 并且我自动收到了下面的东西!');
                console.log(arg);
}

class Test {
    @decoratorFn
    static staticProp: string = '静态属性';

    @decoratorFn
    static staticFn(): void {
            console.log('静态方法');
    }

    @decoratorFn
    instanceProp: string = '实例属性';

    @decoratorFn
    instanceFn(): void {
            console.log('实例方法');
    }
}

装饰器作用
装饰类
  • 在类上使用装饰器, 装饰函数会自动接收到一个值: 被装饰的类
  • 拿到类后我们可以修改类的一些东西, 比如静态属性

[AppleScript] 纯文本查看 复制代码
// 增强类的静态成员
function addFeature(target: any): void {
  let newFeature = ['拉', '撒', '睡'];
  if(target.feature) {
          target.feature.push(...newFeature);
  }
}

@addFeature
class Person {
        static feature: string[] = ['吃', '喝'];
}

@addFeature
class Cat {
        static feature: string[] = ['吃', '喝'];
}

// 测试
console.log(Person.feature);    // ['吃', '喝', '拉', '撒', '睡']
console.log(Cat.feature);         // ['吃', '喝', '拉', '撒', '睡']

装饰器优点
  • 装饰器的好处是在无需做任何变动的情况下实现了功能增强
  • 装饰器也是复用的另外一种形式, 装饰器的特点是目的性很强, 在多人项目中很容易让别人看懂你的意图
  • 下面是不用装饰器的代码, 仍然可以实现相同的功能, 但无论是代码还是语义, 都不如装饰器优雅

[AppleScript] 纯文本查看 复制代码
function feature(target: any): void {
  target.feature = ['吃', '喝', '拉', '撒', '睡'];
}

class Person {}
class Dog {}

// 实现了和装饰器同样的效果, 但是写起来明显不如装饰器语法来的简单
// 同时语义上无法直观的看出调用feature方法是为了增强类, 谁知道feature内部干了什么
feature(Person);
feature(Dog);

装饰方法
  • 在方法上使用装饰器, 装饰函数会自动接收到三个值: (类|原型) 方法名 描述对象
  • 静态方法第一个参数为类, 实例方法第一个参数为原型
  • 拿到这些值后, 我们可以修改或者替换方法定义

[AppleScript] 纯文本查看 复制代码
function executeTime(target: any, name: string, msg: any): void {
  // 缓存旧方法
  let oldFn = msg.value;

  // 赋值新方法, 新方法对旧方法进行了功能扩展
  msg.value = function(...arg: any[]) {
    let date = Date.now();
    oldFn.call(this, ...arg);
          console.log(`执行了: ${Date.now() - date} 毫秒`);
  }
}

class Person {

        @executeTime
  static getTotal(): void {
    console.log('70亿');
  }

  @executeTime
  study(time: number): void {
          for(let i =0; i < time; i++) {
                  console.log('学习');
          }
  }
}

// 测试
new Person().study(1000);
Person.getTotal()

装饰属性
  • 在属性上使用装饰器, 装饰函数会自动接收到两个值: (类|原型) 方法名
  • 静态属性第一个参数为类, 实例属性第一个参数为原型

[AppleScript] 纯文本查看 复制代码
function getSet(...arg: any[]): void {
    let classOrPrototype = arg[0];  // 类或原型
    let propName = arg[1];           // 属性名称

    // 根据属性名称添加对应的set方法
    classOrPrototype[propName + 'Set'] = function(val: any): void {
        this[propName] = val;
    }

    // 根据属性名称添加对应的get方法
    classOrPrototype[propName + 'Get'] = function(): any {
        return this[propName];
    }
}

class Person {
    @getSet
    name: string = '匿名';

    @getSet
    static total: number = 0;
}

// 测试
Person.totalSet(100);
Person.totalGet();

let fang: Person = new Person();
fang.nameSet('芳芳');
fang.nameGet();

装饰器工厂 - 可传参的装饰器
  • 装饰器工厂的写法就是函数返回另一个函数, 这种写法好处是可以接收用户传递的参数
  • 这样我们就可以根据用户的参数进行个性化定制

语法
[AppleScript] 纯文本查看 复制代码
// 外层的函数可收到用户传递的参数
function test(param: string): any {
  console.log(param);

  // 内层函数可收到装饰器自动传递的参数, 说白了就是以前的装饰器函数
        return function(target: any): void {
          console.log(target);
        }
}

// 这里的装饰器语法相比以前多了小括号调用与传参
@test('可以传参?')
class Person {}
// 装饰器工厂内部返回一个函数, 内部的函数就是我们之前写的装饰器函数
function getZhuangshiqi(a: number) {

    // 这个装饰器函数可以自动接收到一些值, 因为我们这个案例装饰了类, 那么就应该得到一个类
    return function(target: any) {

        // 现在这个装饰器函数即可以使用用户传过来的参数, 也可以使用自动传过来的参数
        console.log(a);
        console.log(target);
    }
}

// 用户现在可以调用传参
@getZhuangshiqi(123)
class Person{}

使用范例
[AppleScript] 纯文本查看 复制代码
function feature(level: string): any {
        return function(target: any): void {
          // 高等生物
          if(level === '高等') {
            target.feature.push('玩', '乐');
          }
          // 低等生物
          else if(level === '低等') {
            target.feature.push('拉', '撒');
          }
        }
}

@feature('高等')
class Person {
        static feature: string[] = ['吃', '喝'];
}

@feature('低等')
class Dog {
        static feature: string[] = ['吃', '喝'];
}

补充

  • 一个类/属性或方法可以同时使用多个装饰器
  • 这些装饰器是按照从下向上顺序执行的, 即上面的装饰器权限高

[AppleScript] 纯文本查看 复制代码
function decorator1(...arg: any[]) {
        console.log('装饰器1');
}

function decorator2(...arg: any[]) {
        console.log('装饰器2');
}

function decorator3(...arg: any[]) {
        console.log('装饰器3');
}

@decorator1
@decorator2
@decorator3
class Person {}






1 个回复

倒序浏览
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马