學(xué)習(xí)裝飾器之前我們首先要明確一個(gè)概念:
裝飾器本質(zhì)上是一個(gè)函數(shù),@expression
的形式其實(shí)是一個(gè)語(yǔ)法糖根悼, expression
求值后必須也是一個(gè)函數(shù),它會(huì)在運(yùn)行時(shí)被調(diào)用士败,被裝飾的聲明信息做為參數(shù)傳入控汉。
在 Typescript
中使用裝飾器需要我們?cè)?tsconfig.json
里面開(kāi)啟支持選項(xiàng) experimentalDecorators
。
// tsconfig.json
"experimentalDecorators": true
類(lèi)裝飾器
聲明一個(gè)函數(shù)去給 Class
的 age
屬性賦值:
function addAge(constructor: Function) {
constructor.prototype.age = 22;
}
@addAge
class Person {
name: string;
age!: number;
constructor() {
this.name = "朱小明"
}
}
let p = new Person();
console.log(p.age); //22
這段代碼實(shí)際上基本等同于:
Person = addAge(function Person() { ... });
方法裝飾器
方法裝飾器聲明在一個(gè)方法的聲明之前钟病,它會(huì)被應(yīng)用到方法的屬性描述符descriptor
上萧恕,可以用來(lái)監(jiān)視,修改或者替換方法定義肠阱。
方法裝飾器會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用票唆,傳入3個(gè)參數(shù):
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類(lèi)的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類(lèi)的原型對(duì)象屹徘。
- 成員的名字走趋。
- 成員的屬性描述符。
如果方法裝飾器返回一個(gè)值噪伊,它會(huì)被用作方法的屬性描述符簿煌。如果代碼輸出目標(biāo)版本小于ES5
返回值會(huì)被忽略氮唯。
class Person {
name: string;
constructor(name:string) {
this.name = name;
}
@fun(false)
static say() {
return "hello" + this.name
}
}
function fun(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("target",target);
console.log("propertyKey",propertyKey);
descriptor.enumerable = value
console.log(descriptor);
}
}
const p = new Person("zhangsan")
輸出結(jié)果如下:
target [Function: Person] { say: [Function] }
propertyKey say
{
value: [Function],
writable: true,
enumerable: false,
configurable: true
}
當(dāng) @fun(false)
被調(diào)用時(shí),它會(huì)修改屬性描述符的 enumerable
屬性姨伟。
訪問(wèn)器裝飾器
TypeScript
不允許同時(shí)裝飾一個(gè)成員的get
和set
訪問(wèn)器惩琉,一個(gè)成員的所有裝飾的必須應(yīng)用在文檔順序的第一個(gè)訪問(wèn)器上。這是因?yàn)槎峄模谘b飾器應(yīng)用于一個(gè)屬性描述符時(shí)瞒渠,它聯(lián)合了get
和set
訪問(wèn)器,而不是分開(kāi)聲明的技扼。
訪問(wèn)器裝飾器被調(diào)用時(shí)伍玖,傳入3個(gè)參數(shù):
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類(lèi)的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類(lèi)的原型對(duì)象淮摔。
- 成員的名字私沮。
- 成員的屬性描述符。
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
@configurable(false)
get getName() {
return this.name
}
}
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(descriptor);
descriptor.configurable = value
}
}
const p = new Person("丁大寶")
console.log(p.getName);
輸出結(jié)果如下:
{
get: [Function: get],
set: undefined,
enumerable: true,
configurable: true
}
丁大寶
屬性裝飾器
屬性裝飾器聲明在一個(gè)屬性聲明之前和橙,屬性裝飾器表達(dá)式在運(yùn)行時(shí)被調(diào)用仔燕,傳入下列2個(gè)參數(shù):
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類(lèi)的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類(lèi)的原型對(duì)象魔招。
- 成員的名字晰搀。
注意:屬性描述符不會(huì)做為參數(shù)傳入屬性裝飾器
class Person{
@format("尼古拉斯·趙四")
name: string|undefined;
constructor() {
}
}
function format(value: string) {
return function (target: any, propertyKey: string) {
target[propertyKey] = value
}
}
const p = new Person()
console.log(p.name); //尼古拉斯·趙四
參數(shù)裝飾器
參數(shù)裝飾器聲明在一個(gè)參數(shù)聲明之前(緊靠著參數(shù)聲明)。
參數(shù)裝飾器表達(dá)式需要傳入下列3個(gè)參數(shù):
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類(lèi)的構(gòu)造函數(shù)办斑,對(duì)于實(shí)例成員是類(lèi)的原型對(duì)象外恕。
- 參數(shù)名稱(chēng)
- 參數(shù)的索引
function getParams(target: Object, propertyKey: string, index: number) {
console.log(target,propertyKey,index);
}
class Person {
getInfo(@getParams name: string, @getParams age: number) {
return `${name}${age}歲了`
}
}
const p = new Person();
console.log(p.getInfo("zhangsan",20));
輸出結(jié)果如下:
Person { getInfo: [Function] } getInfo 1
Person { getInfo: [Function] } getInfo 0
zhangsan20歲了
裝飾器工廠
如果我們需要幾個(gè)裝飾器分別裝飾到不同的成員上。我們可能會(huì)用下面這種寫(xiě)法:
@logClass
class Person{
@logProperty
name: string;
constructor(name:string) {
this.name = name;
}
@logMethod
getName() {
return this.name
}
}
// 類(lèi)裝飾器
function logClass(target: typeof Person) {
console.log(target);
}
// 屬性裝飾器
function logProperty(target: any, propertyKey: string) {
console.log(propertyKey);
}
// 方法裝飾器
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(descriptor);
}
雖然可以達(dá)到我們想要的效果乡翅,但我們還可以繼續(xù)優(yōu)化鳞疲,用一個(gè)裝飾器工廠來(lái)進(jìn)一步抽象上述代碼:
function log(...args: any[]) {
switch (args.length) {
case 1:
return logClass.apply(this, args);
case 2:
return logProperty.apply(this, args);
case 3:
return logMethod.apply(this, args)
default:
throw new Error("error")
}
}
裝飾器組合
在TypeScript
里,當(dāng)多個(gè)裝飾器應(yīng)用在一個(gè)聲明上時(shí)會(huì)進(jìn)行如下步驟的操作:
- 由上至下依次對(duì)裝飾器表達(dá)式求值蠕蚜。
- 求值的結(jié)果會(huì)被當(dāng)作函數(shù)尚洽,由下至上依次調(diào)用。
如果我們使用[裝飾器工廠]的話(huà)靶累,可以通過(guò)下面的例子來(lái)觀察它們求值的順序:
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
輸出結(jié)果如下:
f(): evaluated
g(): evaluated
g(): called
f(): called
裝飾器求值順序:
- 參數(shù)裝飾器腺毫,然后依次是方法裝飾器,訪問(wèn)符裝飾器挣柬,或?qū)傩匝b飾器應(yīng)用到每個(gè)實(shí)例成員潮酒。
- 參數(shù)裝飾器,然后依次是方法裝飾器邪蛔,訪問(wèn)符裝飾器急黎,或?qū)傩匝b飾器應(yīng)用到每個(gè)靜態(tài)成員。
- 參數(shù)裝飾器應(yīng)用到構(gòu)造函數(shù)。
- 類(lèi)裝飾器應(yīng)用到類(lèi)叁熔。