TS-装饰器

226 阅读6分钟
  • 装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。
  • 通俗的讲,装饰器就是一个方法,可以注入到类,方法,属性或参数上来扩展类,方法,属性或参数的功能。
  • 常见的装饰器有:类装饰器,属性装饰器,方法装饰器,参数装饰器
  • 装饰器的写法:普通装饰器(无法传参),装饰器工厂(可传参)
  • 装饰器是过去几年中 JS 最大的成就之一,已是 ES7 的标准特性之一。

类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。

  • 传入一个参数。类装饰器应用于类构造函数,可以用来监视修改或替换类定义。

一 普通装饰器(无法传参)

// 装饰器
function logClass(params: any) {
    // params 就是当前类,可以使用params扩展当前类
    console.log(params); // ƒ HttpClient() { }
    // 动态扩展属性
    params.prototype.url = '普通装饰器-----在不改变原来类的前提下,动态扩展属性';
    // 动态扩展方法
    params.prototype.run = function() {
        console.log('普通装饰器-----在不改变原来类的前提下,动态扩展方法');
    }
}

// --普通装饰器(不可传参)
// 不带参数时,获得的直接是 该类
@logClass
class HttpClient {
    constructor() {

    }
    getData(): void {

    }
}
let http: any = new HttpClient();
console.log(http.url); // 普通装饰器-----在不改变原来类的前提下,动态扩展属性
http.run(); // 普通装饰器-----在不改变原来类的前提下,动态扩展方法

二 装饰器工厂(可传参)

// 装饰器工厂----可传参
function logClass(params: string) {
    return function(target: any) {
        console.log('params----', params); // params---- http://www.baidu.com/api
        console.log('target----', target); // target---- ƒ HttpClient() { }
        // 动态扩展属性
        target.prototype.url = params;
        // 动态扩展方法
        target.prototype.run = function() {
            console.log('工厂装饰器-----在不改变原来类的前提下,动态扩展方法');
        }
    }
}
// 不带参数时,获得的直接是 参数+该类
@logClass('http://www.baidu.com/api')
class HttpClient {
    constructor() {

    }
}
let http: any = new HttpClient();
console.log(http.url); // http://www.baidu.com/api
http.run(); // 工厂装饰器-----在不改变原来类的前提下,动态扩展方法

三 类装饰器重载构造函数

  • 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数
  • 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
  • 执行顺序:装饰器里的内容--->类的构造函数--->类中的方法(针对下方实例)。
// 类装饰器重载构造函数
function logClass(target: any) {
    console.log(target);
    // ƒ HttpClient() {
    //     this.apiUrl = 'apiUrl';
    //     console.log(11);
    // }
    
    return class extends target {
        apiUrl: any = '我是类装饰器的数据';
        getData(): void {
            console.log(`这是类装饰器里面的apiUrl----${this.apiUrl}`); // 这是类装饰器里面的apiUrl----我是类装饰器的数据
        }
    }
}

// 不带参数时,获得的直接是 该类
@logClass
class HttpClient {
    public apiUrl: string | undefined;
    constructor() {
        this.apiUrl = 'apiUrl';
        console.log(11); // 11
    }
    getData(): void {
        console.log(`这是构造函数里面的apiUrl-${this.apiUrl}`);
    }
}
let http = new HttpClient();
http.getData(); // 这是构造函数里面的apiUrlapiUrl

输出

ƒ HttpClient() {
    this.apiUrl = 'apiUrl';
    console.log(11);
}
11
这是类装饰器里面的apiUrl----我是类装饰器的数据

属性装饰器

  • 属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数
  • 1 对于静态成员来说是类的构造函数,对于实例成员类的原型对象
  • 2 成员的名字
// 属性装饰器
function logProperty(params: any) {
    console.log('属性装饰器--params-', params); // 属性装饰器--params- http://www.itying.com/api
    return function(target: any, attr: any) {
        console.log('属性装饰器-target', target);
        // 属性装饰器-target {getData: ƒ, getData_1: ƒ, constructor: ƒ}
        // getData: ƒ ()
        // getData_1: ƒ ()
        // url: "http://www.itying.com/api"
        // constructor: ƒ HttpClient()
        // __proto__: Object
        // ... ...

        console.log('属性装饰器-attr', attr); // 属性装饰器-attr url
        target[attr] = params; // 将属性装饰器传入的参数赋值给
    }
}

class HttpClient {
    @logProperty('http://www.itying.com/api')
    public url: any | undefined;
    public url_1: any | undefined;
    constructor() {
        this.url_1 = '这是url_1';
    }
    getData(): void {
        console.log('this.url--', this.url); // this.url-- http://www.itying.com/api
    }
    getData_1(): void {
        console.log('this.url_1--', this.url_1);
    }
}
let http: any = new HttpClient();
http.getData();

输出

属性装饰器--params- http://www.itying.com/api
属性装饰器-target {getData: ƒ, getData_1: ƒ, constructor: ƒ}getData: ƒ ()getData_1: ƒ ()url: "http://www.itying.com/api"constructor: ƒ HttpClient()__proto__: Objectconstructor: ƒ Object()hasOwnProperty: ƒ hasOwnProperty()isPrototypeOf: ƒ isPrototypeOf()propertyIsEnumerable: ƒ propertyIsEnumerable()toLocaleString: ƒ toLocaleString()toString: ƒ toString()valueOf: ƒ valueOf()__defineGetter__: ƒ __defineGetter__()__defineSetter__: ƒ __defineSetter__()__lookupGetter__: ƒ __lookupGetter__()__lookupSetter__: ƒ __lookupSetter__()get __proto__: ƒ __proto__()set __proto__: ƒ __proto__()
属性装饰器-attr url
this.url-- http://www.itying.com/api

方法装饰器

它会被应用到方法的属性描述符上,可以用来监视、修改或者替换方法定义。方法装饰器会在运行时传入下列3个参数:

  • 1 对于静态成员来说是类的构造函数,对于实例成员类的原型对象
  • 2 成员的名字。
  • 3 成员的属性描述符。

一 可以扩展属性、方法

// 方法装饰器
function GET(params: any) {
    console.log('方法装饰器--params-', params); // 方法装饰器--params- http://www.baidu.com
    return function(target: any, methodName: any, desc: any) {
        console.log('方法装饰器-target', target);
        // 方法装饰器-target 
        // {getData: ƒ, getData_1: ƒ, constructor: ƒ}
        // getData: ƒ ()
        // getData_1: ƒ ()
        // name: "xxx"
        // run: ƒ ()
        // constructor: ƒ HttpClient()
        // __proto__: Object

        console.log('方法装饰器-methodName', methodName); // 方法装饰器-methodName getData
        console.log('方法装饰器-desc', desc);
        // 方法装饰器-desc 
        // {writable: true, enumerable: true, configurable: true, value: ƒ}
        // configurable: true
        // enumerable: true
        // value: ƒ ()
        // writable: true
        // __proto__: Object

        // 扩展
        target.name = 'xxx';
        target.run = function() {
            console.log('run');
        }
    }
}

class HttpClient {
    public url: any | undefined;
    public url_1: any | undefined;
    constructor() {
        this.url = '这是构造函数的URL';
    }
    
    @GET('http://www.baidu.com')
    getData(): void {
        console.log('this.url--', this.url); // this.url-- 这是构造函数的URL
    }
    getData_1(): void {

    }
}
let http: any = new HttpClient();
http.getData();
http.run(); // run
console.log(http.name); // xxx

输出

方法装饰器--params- http://www.baidu.com
方法装饰器-target {getData: ƒ, getData_1: ƒ, constructor: ƒ}getData: ƒ ()getData_1: ƒ ()name: "xxx"run: ƒ ()constructor: ƒ HttpClient()__proto__: Object
方法装饰器-methodName getData
方法装饰器-desc {writable: true, enumerable: true, configurable: true, value: ƒ}configurable: trueenumerable: truevalue: ƒ ()writable: true__proto__: Object
this.url-- 这是构造函数的URL
run
xxx

二 可修改方法内容

// 方法装饰器 -- 可修改方法内容
function GET(params: any) {
    console.log('方法装饰器--params-', params); // 方法装饰器--params- http://www.baidu.com
    return function(target: any, methodName: any, desc: any) {
        console.log('方法装饰器-target', target);
        // 方法装饰器-target 
        // {getData: ƒ, getData_1: ƒ, constructor: ƒ}
        // getData: ƒ ()
        // getData_1: ƒ ()
        // name: "xxx"
        // run: ƒ ()
        // constructor: ƒ HttpClient()
        // __proto__: Object

        console.log('方法装饰器-methodName', methodName); // 方法装饰器-methodName getData
        console.log('方法装饰器-desc', desc);
        // 方法装饰器-desc 
        // {writable: true, enumerable: true, configurable: true, value: ƒ}
        // configurable: true
        // enumerable: true
        // value: ƒ ()
        // writable: true
        // __proto__: Object

        // 修改装饰器的方法  把装饰器方法里面传入的所有参数改为 string类型
        // 1 保存当前的方法
        let oMethod = desc.value;
        desc.value = function(...args: any[]) {
            args = args.map(value => {
                return String(value);
            })
            console.log('这是通过方法装饰器修改后的 方法'); // 这是通过方法装饰器修改后的 方法
            console.log(args); // (3) ["123", "23", "true"]
            oMethod.apply(this, args); // this 指得是当前的function
        }

    }
}

class HttpClient {
    public url: any | undefined;
    public url_1: any | undefined;
    constructor() {
        this.url = '这是构造函数的URL';
    }
    @GET('http://www.baidu.com')
    getData(): void {
        console.log('this.url--', this.url); // this.url-- 这是构造函数的URL
    }
    @GET('http://www.baidu.com/api')
    getData_1(...args: any[]): void {
        console.log('args--', args); // (3) ["34", "45", "false"]
    }
}
let http: any = new HttpClient();
http.getData(123,'23',true);
http.getData_1(34,'45',false);

输出

方法装饰器--params- http://www.baidu.com
方法装饰器-target {getData: ƒ, getData_1: ƒ, constructor: ƒ}
方法装饰器-methodName getData
方法装饰器-desc {writable: true, enumerable: true, configurable: true, value: ƒ}
方法装饰器--params- http://www.baidu.com/api
方法装饰器-target {getData: ƒ, getData_1: ƒ, constructor: ƒ}
方法装饰器-methodName getData_1
方法装饰器-desc {writable: true, enumerable: true, configurable: true, value: ƒ}
这是通过方法装饰器修改后的 方法
(3) ["123", "23", "true"]
this.url-- 这是构造函数的URL
这是通过方法装饰器修改后的 方法
(3) ["34", "45", "false"]
args-- (3) ["34", "45", "false"]

方法参数装饰器

参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列3个参数

  • 1 对于静态成员来说是类的构造函数,对于实例成员类的原型对象
  • 2 参数的名字。
  • 3 参数在函数参数列表中的索引
    先调用 参数装饰器
// ------- 方法参数装饰器
function methodParams(params: any) {
    console.log('方法参数装饰器---params--', params);
    return function(target: any, methodName: any, paramsIndex: any) {
        console.log('方法参数装饰器---target--', target);
        console.log('方法参数装饰器---methodName--', methodName);
        console.log('方法参数装饰器---paramsIndex--', paramsIndex);
    }
}
class HttpClient {
    public url: any | undefined;
    constructor() {
        this.url = '这是构造函数的URL';
    }
    getData(@methodParams('uuid') uuid: any, @methodParams('apptoken') apptoken: any, @methodParams('hh') hh: any): void {
        console.log('this.url--', this.url); // this.url-- 这是构造函数的URL
        console.log(`uuid--${uuid},apptoken--${apptoken},hh--${hh}`); // this.url-- 这是构造函数的URL
    }
}
let http: any = new HttpClient();
http.getData(123,'23',true);

输出

方法参数装饰器---params-- uuid
方法参数装饰器---params-- apptoken
方法参数装饰器---params-- hh
方法参数装饰器---target-- {getData: ƒ, constructor: ƒ}getData: ƒ (uuid, apptoken, hh)constructor: ƒ HttpClient()__proto__: Object
方法参数装饰器---methodName-- getData
方法参数装饰器---paramsIndex-- 2
方法参数装饰器---target-- {getData: ƒ, constructor: ƒ}getData: ƒ (uuid, apptoken, hh)constructor: ƒ HttpClient()__proto__: Object
方法参数装饰器---methodName-- getData
方法参数装饰器---paramsIndex-- 1
方法参数装饰器---target-- {getData: ƒ, constructor: ƒ}getData: ƒ (uuid, apptoken, hh)constructor: ƒ HttpClient()__proto__: Object
方法参数装饰器---methodName-- getData
方法参数装饰器---paramsIndex-- 0
this.url-- 这是构造函数的URL
uuid--123,apptoken--23,hh--true

装饰器执行顺序

属性装饰器-->方法装饰器-->方法参数装饰器-->类装饰器。
若 同样的装饰器有多个,顺序:从右到左,或从下到上

// 类装饰器
function logClass(params: any) {
    return function(target: any) {
        console.log('类装饰器');
    }
}
// 类装饰器1
function logClass1(params: any) {
    return function(target: any) {
        console.log('类装饰器1');
    }
}
// 属性装饰器
function Url(params: any) {
    return function(target: any, methodName: any) {
        console.log('属性装饰器');
    }
}
// 方法装饰器
function GET(params: any) {
    return function(target: any, methodName: any, desc: any) {
        console.log('方法装饰器');
    }
}
// 参数装饰器
function methodParams(params: any) {
    return function(target: any, methodName: any, paramsIndex: any) {
        console.log('参数装饰器');
    }
}
@logClass1('HttpClient1')
@logClass('HttpClient')
class HttpClient {
    @Url('http://www.baidu.com/api')
    public url: any | undefined;
    constructor() {
        this.url = '这是构造函数的URL';
    }
    @GET('/api/list')
    getData(@methodParams('username') username: string, @methodParams('password') password: string): void {
        console.log(`URL:${this.url}--username:${username}--password:${password}`);
    }
}
let http: any = new HttpClient();
http.getData('admin','123456');