因主要的技術棧是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