因主要的技术栈是Angular,对于Angular采用的装饰器特别认可,是一种优雅的拦截JS的方式。
TC39的装饰器提案其实共3个:class类和类属性装饰器、function函数装饰器、parameter方法参数装饰器,后2个仍处于Stage 0中,因此本文只针对class装饰器
目前Decorator仍处于Stage 2的阶段,不知道能否在ES2019(ES10)中推出,但一个提案只要能进入Stage 2,就基本会包括在以后的正式标准里面。
有N多文章写道Decorator是ES2016(ES7)推出的,不知道这是从哪里流传出来的,ES2016最终特性根本就没有Decorator,可能的原因:Decorator只是有望在ES2016推出的,实际上并没有。
以Angular中的一个组件为例,来说明装饰器的主要用法和装饰器到底是什么:
@Component({
selector: 'app-transfer-common',
templateUrl: './transfer-common.component.html',
styleUrls: ['./transfer-common.component.scss']
})
@AutoUnsubscribe()
export class TransferCommonComponent implements OnInit {
whichRouter: String;
btnShowStatus: ShowOrHideBtn;
queryParamsSubscribe: any;
userInfo: any = this.commonUserService.getUserInfo();
i18ns_common;
constructor(
private router: Router,
private route: ActivatedRoute,
private returnVariousBtn: ReturnVariousRouterParamsService,
private commonUserService: CommonUserService,
private httpService: HttpService,
private translate: TranslateService,
) { }
ngOnInit() {
}
// 跳转到新增
@getProperty
gotoAdd() {
}
/* ngOnDestroy() {
this.queryParamsSubscribe.unsubscribe();
} */
}
一.类装饰器
AutoUnsubscribe是一个自动取消订阅的装饰器,可传入参数指定某个可订阅对象不自动取消订阅。如不需要指定,可取消外层高阶函数
export function AutoUnsubscribe(params: string) {
return function (constructor) {
console.log(constructor);
console.log(constructor.prototype);
const originNgDestory = constructor.prototype.ngOnDestroy;
constructor.prototype.ngOnDestroy = function () {
console.log(this);
console.log(this.constructor);
console.log(constructor);
for (const property of Object.values(this)) {
if (property && (typeof property.unsubscribe === 'function')) {
console.log(property);
}
}
originNgDestory && typeof originNgDestory === 'function' && originNgDestory.apply(this);
};
};
}
1.类装饰器仅仅是一个接受一个参数的、被装饰的类的构造函数,常用于修改、添加类的原型方法
2.类装饰器传入的constructor就是类的constructor(特指constructor(){}),如下图红色部分.类装饰器作用于类的 constructor,并且观察、修改或者替换一个类的定义。
3.this是整个class,如图天蓝色外框
4.特殊的是:类的属性方法在类的proto中,其他的是类的直接子集,如图靑蓝色部分
4.参数constructor === this.constructor
5.执行订阅的时候,订阅的是一个Subscribe对象,property.unsubscribe是从原型链上获取的unsubscribe,如下图绿色部分
二.类属性(方法)装饰器
// 下一个事件队列中执行的装饰器
export const timeoutDecorator = function (milliseconds: number = 0) {
return function (target, key, descriptor: any) {
console.log(target); // 即整个对象
console.log(key); // 装饰器修饰的某个key
console.log(descriptor);
const originalMethod = descriptor.value; // value即这个key对应的方法
/** 当执行gotoAdd方法时,即先执行以下函数 */
descriptor.value = function (...args) {
console.log(args);
setTimeout(() => {
originalMethod.apply(this, args); // gotoAdd方法调用
}, milliseconds);
};
console.log(descriptor);
return descriptor;
};
};
export const getProperty = function (target, name, descriptor) {
console.log(target);
console.log(name);
console.log(descriptor);
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
console.log(args);
originalMethod.apply(this, args);
};
console.log(descriptor);
return descriptor;
};
1.类方法装饰器是一个函数,函数参数就是Object.defineProperty中的三个参数即:target(目标对象)、key(调用的属性名)、descriptor(调用的属性的描述,包括configurable、enumerable、writable、value)
2.类方法装饰器的作用是把类中的方法放入装饰器中执行,个人理解类似于管道或者拦截器
3.类的普通属性(不是方法的)也可以添加装饰器,比如Angular中的@ViewChild,@ViewContent
三.修饰器本质就是编译时执行的函数
注意,不管哪种修饰器,对类的行为的改变,是代码编译时(初始化时)发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。
表现上:添加上装饰器后,装饰器中descriptor.value = function(...args) ...... 函数外的内容会先执行.然后在每次触发类中的修饰的方法是会才会调用descriptor.value = function(...args) ...... 中的内容,args是指触发此方法时的实际参数.再通过apply方法,调用需要修饰的类方法,注意不一定是指向this,也有可能是target,具体看情况
四.特别注意:this
箭头函数中的this和普通函数的this是不同的,注意函数体内是否有this,有的话就不要用箭头函数了,详情请搜索箭头函数的this
划重点
1.类装饰器和类方法装饰器是不同的,但本质都是一个函数
2.类装饰器常用于修改、添加类的原型方法,类方法装饰器用于拦截类方法,需要掌握具体写法
3.注意箭头函数和this的搭配使用问题
参考资料:
ECMAScript 6 入门
ECMAScript proposals
decorator