typescript 装饰器全集

装饰器主要与类结合使用

装饰器分为 1类装饰器、2属性装饰器、3方法装饰器、4方法参数装饰器

类装饰器

类装饰器的简单使用

// 类装饰器 params 指的是类本身,在浏览器中打印出来就是构造函数本身
function logClass (params: any) {
    
    
  console.log(params)
}

@logClass
class HttpClient {
    
    
  apiUrl:string;
  constructor(apiUrl:string) {
    
    
    this.apiUrl = apiUrl
  }
}

类装饰器添加属性和方法

// 装饰器
function logClass(params: any) {
    
    
  // 放到构造函数的原型上
  params.prototype.method = 'POST';
  params.prototype.setMethod = (value: 'POST' | 'GET') => {
    
    
    params.prototype.method = value;
  };
}
// 装饰器的使用
@logClass
class HttpClient {
    
    
  apiUrl: string;
  constructor(apiUrl: string) {
    
    
    this.apiUrl = apiUrl;
  }
}

const http1:any = new HttpClient('xxxx');

console.log(http1.apiUrl) // xxxx
console.log(http1.method) // POST
http1.setMethod('GET')
console.log(http1.method) // GET

类装饰器工厂(可以给装饰器函数传参)

logclassFunction函数调用 返回的函数才是真正类装饰器
所以 logClassFunction 只是类装饰器工厂,用来生产类装饰器(即返回的函数)

// 装饰器工厂
function logClassFunction(slefArgus: {
    
     name: string; age: number }) {
    
    
  console.log(slefArgus);
  return (params: any) => {
    
    
    params.prototype.method = 'POST';
    params.prototype.setMethod = (value: 'POST' | 'GET') => {
    
    
      params.prototype.method = value;
    };
    params.prototype.data = slefArgus;
  };
}
// 装饰器工厂函数的调用方式
@logClassFunction({
    
     name: 'dx', age: 18 })
class HttpClient {
    
    
  apiUrl: string;
  constructor(apiUrl: string) {
    
    
    this.apiUrl = apiUrl;
  }
}

const http1: any = new HttpClient('xxxxx');

console.log(http1.apiUrl); // xxxx
console.log(http1.method); // POST
http1.setMethod('GET');
console.log(http1.method); // GET
console.log(http1.data); // {name: 'dx',age:18}

类装饰器重写类

类装饰器可以对类进行重写,这样类本身的属性和方法都会被替换,但一定要重写每一个必须的属性和方法,否则会类型报错

function reLogClass(targets: any) {
    
    
  // 返回一个新的class 扩展 targets (targets就是被重构的类)
  return class extends targets {
    
    
    apiUrl: string = '这是重构的url';
    getData() {
    
    
      return {
    
    
        url: this.apiUrl,
      };
    }
  };
}

@reLogClass
class HttpClient {
    
    
  apiUrl: string;
  constructor(apiUrl: string) {
    
    
    this.apiUrl = apiUrl;
  }
}

const http1: any = new HttpClient('xxxxx');

console.log(http1.apiUrl); //这是重构的url 
console.log(http1.getData()); // {url: '这是重构的url'}

因为 HttpClient 这个类里面的内容被 装饰器重构了,所以打印出来的apiurl属性是以装饰器函数返回的class为准

属性装饰器

属性装饰器,用来修改类里面的属性,既然是修改属性,就需要传参,而传参我们都知道,需要用装饰器工厂,与类装饰器工厂不同,属性装饰器工厂返回的是一个属性装饰器而已(都是返回一个方法)。

function logProperty(params: string) {
    
    
  return (targets: any, attr: string) => {
    
    
    console.log(params, targets, attr);
    // targets是 constructor attr是属性的名称(apiUrl), params是传过来的参数(这是属性装饰器)
    // 这只能设置到原型上
    targets[attr] = params;
  };
}

class HttpClient {
    
    
  @logProperty('这是属性装饰器')
  apiUrl: string | undefined;
  constructor() {
    
    }
}

const http1 = new HttpClient();

console.log(http1.apiUrl); // 这是属性装饰器

在这里插入图片描述
根据我的测试,属性装饰器只能设置默认值,因为通过属性装饰器的参数,只能设置原型上的值。
我们都知道,如果对象实例上没有这个值,它就回去原型上找。如果实例对象上这个值存在,就不会去原型上找(也就是装饰器设置的原型的值无效)。

如果实例的值存在

function logProperty(params: string) {
    
    
  return (targets: any, attr: string) => {
    
    
    console.log(params, targets, attr);
    // targets是 constructor attr是属性的名称(apiUrl), params是传过来的参数(这是属性装饰器)
    // 这只能设置到原型上
    targets[attr] = params;
  };
}

class HttpClient {
    
    
  @logProperty('这是属性装饰器')
  apiUrl: string | undefined = "xxxx";
  constructor() {
    
    }
}

const http1 = new HttpClient();

console.log(http1.apiUrl); // xxxx

方法装饰器

将被装饰的方法替换掉

function logFunction(params: any) {
    
    
  return function (construct: any, functionName: string, functionDesc: any) {
    
    
    // construct 对于静态成员来说 是类的构造函数  对于实例成员是类的原型对象。
    // functionName 是方法的名字
    // functionDesc 是关于方法的一些描述 (一般不用,里面主要是是否可配置configurable 是否可数 enumerable 是否可写 writable 等)
    console.log(construct, functionName, functionDesc);
    // 将原先方法替换掉
    functionDesc.value = () => {
    
    
      return {
    
    
        relUrl: params,
      };
    };

    // 对原来的方法进行修改

  };
}

class HttpClient {
    
    
  apiUrl: string;
  constructor(apiUrl: string) {
    
    
    this.apiUrl = apiUrl;
  }
  @logFunction('xxxxx')
  getData() {
    
    
    return {
    
    
      url: this.apiUrl,
    };
  }
}

const http1 = new HttpClient('www.baidu.com');

console.log(http1.getData()) // {relUrl: "xxxxx"}

修改被装饰的方法

扫描二维码关注公众号,回复: 12855046 查看本文章
function logFunction(params: any) {
    
    
  return function (construct: any, functionName: string, functionDesc: any) {
    
    
    // construct 对于静态成员来说 是类的构造函数  对于实例成员是类的原型对象。
    // functionName 是方法的名字
    // functionDesc 是关于方法的一些描述 (一般不用,里面主要是是否可配置configurable 是否可数 enumerable 是否可写 writable 等)
    console.log(construct, functionName, functionDesc);

    const oMethod = functionDesc.value;

    // 对原来的方法进行修改
    functionDesc.value = function (newUrl: string, method: string) {
    
    
      // 采用对象冒充,改变this,并将参数传入
      oMethod.call(this, newUrl, method);
      return {
    
    
        url: newUrl,
        method,
      };
    };
  };
}

class HttpClient {
    
    
  apiUrl: string;
  constructor(apiUrl: string) {
    
    
    this.apiUrl = apiUrl;
  }
  @logFunction('xxxxx')
  getData(newUrl: string, method: string) {
    
    
    console.log(`${
      
      newUrl}原先`);
  }
}

const http1 = new HttpClient('www.baidu.com');

console.log(http1.getData('www.taobao.com', 'get'));
// www.taobao.com原先
// {url: "www.taobao.com",method: "get"}
// tips: 从最终打印的结果可以发现,原先的方法也被执行了。

方法参数装饰器

方法参数装饰器一般不会用,它能实现的功能用其它装饰器也可以实现。

function logAruguments(params: any) {
    
    
  return (target: any, methodName: string, paramsIndex: number) => {
    
    
    console.log(params, target, methodName, paramsIndex);
    // target 对于静态成员来说 是类的构造函数  对于实例成员是类的原型对象。
    // methodName 参数所在方法的名称
    // paramsIndex 参数所在方法参数的下标
    target.method = params;
  };
}

class HttpClient {
    
    
  apiUrl: string;
  method: string | undefined;
  constructor(apiUrl: string) {
    
    
    this.apiUrl = apiUrl;
  }
  getData(@logAruguments('post') method: string) {
    
    
    return {
    
    
      url: this.apiUrl,
      method: this.method || method,
    };
  }
}

const http1 = new HttpClient('xxxxx');

console.log(http1.getData('get'));
// {url: "xxxxx", method: "post"}
// 因为在方法参数装饰器中给this.method赋值了,所以打印出来不是get

装饰器执行顺序

在实际使用中,可能会多个装饰器一起使用,

官网上关于 装饰器执行的顺序描述

类中不同声明上的装饰器将按以下规定的顺序应用:

参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
参数装饰器应用到构造函数。
类装饰器应用到类。

参数装饰器>方法装饰器>方法参数装饰器 >类装饰器

如果每一种装饰器有多个,就从后往前,从右往左的顺序执行

猜你喜欢

转载自blog.csdn.net/glorydx/article/details/112784468