装饰器主要与类结合使用
装饰器分为 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"}
修改被装饰的方法
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
装饰器执行顺序
在实际使用中,可能会多个装饰器一起使用,
官网上关于 装饰器执行的顺序描述
类中不同声明上的装饰器将按以下规定的顺序应用:
参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
参数装饰器应用到构造函数。
类装饰器应用到类。
参数装饰器>方法装饰器>方法参数装饰器 >类装饰器
如果每一种装饰器有多个,就从后往前,从右往左的顺序执行