本文最后更新于:2020-05-05 23:11:38

1.什么是装饰器

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

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

装饰器的写法:

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

2.类装饰器

类装饰器—-普通装饰器(无法传参)

function logClass(params: any) {
    console.log(params) //当前类

    //TODO ① 拓展-属性
    params.prototype.apiUrl = 'https://www.bilibili.com/video/av38379328/?p=19';

    // TODO ② 拓展-方法
    params.prototype.get = function(){
        console.log('我是拓展的方法' + this.apiUrl);
    }

}

@logClass
class HttpClient {
    constructor() {

    }
    getData() {

    }
}

let http: any = new HttpClient();

//TODO ① 访问拓展的属性
console.log(http.apiUrl); //https://www.bilibili.com/video/av38379328/?p=19

//TODO ② 访问拓展的方法
http.get(); // 我是拓展的方法https://www.bilibili.com/video/av38379328/?p=19

类装饰器—-装饰器工厂(可传参)—返回一个函数

function logClass(params: string) {
    return function (target: any) {
        console.log(params) // 传入的参数 https://www.bilibili.com/video/av38379328/?p=19
        console.log(target) // 当前类

        // 拓展 apiUrl 属性,其值为传入的 params;
        target.prototype.apiUrl = params;
    }
}

@logClass('https://www.bilibili.com/video/av38379328/?p=19')
class HttpClient {
    constructor() {

    }
    getData() {

    }
}

let http: any = new HttpClient();
console.log('使用装饰器工厂拓展的属性', http.apiUrl); //使用装饰器工厂拓展的属性 https://www.bilibili.com/video/av38379328/?p=19

类装饰器-重载构造函数的例子—返回一个继承于当前类的子类

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

function logClass(target: any) {
    console.log(target) // target 当前类
    return class extends target {
        // TODO ① 重载apiUrl的值
        apiUrl = '我是修改后的数据  https://www.bilibili.com/video/av38379328/?p=19';

        // TODO ② HttpClient类里面的方法被重载了
        getData() {
            this.apiUrl += '---';
            console.log(this.apiUrl)
        }
    }
}

@logClass
class HttpClient {
    public apiUrl: string | undefined;
    constructor() {
        // TODO ① 初始化apiUrl的值
        this.apiUrl = "我是constructor里面的apiUrl";
    }

    // TODO ② HttpClient类里面的方法
    getData() {
        console.log(this.apiUrl);
    }
}

let http: any = new HttpClient();
http.getData(); // 我是修改后的数据  https://www.bilibili.com/video/av38379328/?p=19---

3.属性装饰器

属性装饰器表达式会在运行时当作函数被调用,传入2个参数
1.对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
2.成员的名称。

// 1.实例成员
function logProperty(params: string) {
    return function (target: any, attrName: string) {
        console.log(params); // http://www.baidu.com

        console.log(target); // TODO ① HttpClient 这个类的原型对象(即:HttpClient.prototype)  HttpClient { getData: [Function] }
        console.log(attrName) //apiUrl

        target[attrName] = params;
    }
}
class HttpClient {
    @logProperty('http://www.baidu.com')
    public apiUrl: string | undefined; //TODO ① 这里声明为实例属性

    constructor() {
    }

    getData() {
        console.log(this.apiUrl);
    }
}

let http: any = new HttpClient();
http.getData(); // http://www.baidu.com
//2.静态成员
function logProperty(params: string) {
    return function (target: any, attrName: string) {
        console.log(params); // http://www.baidu.com/2

        console.log(target);// TODO ② HttpClient 这个类  [Function: HttpClient]
        console.log(attrName) //apiUrl2

        target[attrName] = params; //由于是静态属性 这里就直接把params 挂到类上
    }
}
class HttpClient {

    @logProperty('http://www.baidu.com/2')
    static apiUrl2: string | undefined; //TODO ② 这里声明为静态成员

    constructor() {
    }

    getData() {
        console.log(HttpClient.apiUrl2); // 静态属性通过 HttpClient.属性名 来访问
    }
}

let http: any = new HttpClient();
http.getData(); // http://www.baidu.com/2

4.方法装饰器

它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
方法装饰器表达式会在运行时当作函数被调用,传入3个参数
1.对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
2.成员的名称。
3.成员的属性描述符。

//这里只演示:实例成员
function logFunc(params: string) {
    return function (target: any, funcName: string, desc: any) {
        console.log(params); // http://www.jd.com
        console.log(target); // HttpClient.prototype
        console.log(funcName); // getData
        console.log(desc);
        /*
            { 
                value: [Function],
                writable: true,
                enumerable: true,
                configurable: true 
            }
        */

        // 修改装饰的方法(操作desc.value) 把装饰器方法里面传入的参数全部修改为string类型。
        // 1.保存当前方法
        let oldFunc = desc.value;
        // 2.重写 desc.value
        desc.value = function (...args: any[]) {
            console.log(args); // [123, '456']

            // 3.转换为String
            args = args.map(value => {
                return String(value);
            })
            console.log(args); // [ '123', '456' ]

            // 4.选择替换该方法还是给方法增加功能
            oldFunc.apply(this, args); // 加上这个就会执行HttpClient 类里面的getData 方法。
            // 不加 就是把【HttpClient 类里面的getData 方法】给替换了。
        }
    }
}

class HttpClient {
    apiUrl: string | undefined;
    constructor() {
    }
    @logFunc('http://www.jd.com')
    getData(...args: any[]) {
        console.log(args); // 经过装饰器的修改,这里全部被转为了String类型 [ '123', '456' ]
        console.log('我是getData');
    }
}

let http: any = new HttpClient();
http.getData(123, '456');

5.方法参数装饰器(不常用)

参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元数据,传入3个参数
1.对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
2.方法的名称。
3.参数在函数参数列表中的索引。

function logParams(params: string) {
    return function (target: any, funcName: string, paramsIndex: number) {
        console.log(params) // name         id
        console.log(target) // HttpClient.prototype
        console.log(funcName) // getData
        console.log(paramsIndex) // 1       0
    }
}

class HttpClient {
    url: any | undefined;
    constructor() {
    }
    getData(
        @logParams('id') id: number, //后执行
        @logParams('name') name: string // 先执行
    ) {

    }
}

6.执行顺序

属性装饰器—>方法装饰器—>方法参数装饰器—>类装饰器
如果有多个同样的装饰器都是 从后到前执行


技术      TS

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!