- 裝飾器是一種特殊類型的聲明,本質(zhì)上就是一個(gè)方法,可以注入到類、方法、屬性、參數(shù)上,擴(kuò)展其功能;
- 常見的裝飾器:類裝飾器、屬性裝飾器、方法裝飾器、參數(shù)裝飾器...
- 裝飾器在寫法上有:普通裝飾器(無(wú)法傳參)、裝飾器工廠(可傳參)
- 裝飾器已是ES7的標(biāo)準(zhǔn)特性之一,是過(guò)去幾年JS最大的成就之一!
- 啟用裝飾器:
"compilerOptions": { "experimentalDecorators": true }
類裝飾器
- 類裝飾器在類聲明之前被聲明,應(yīng)用于類構(gòu)造函數(shù),可以監(jiān)視、修改、替換類的定義,傳入一個(gè)參數(shù);
function logClz(params:any) { console.log(params) // class HttpClient } @logClz class HttpClient { constructor() { } }
-
logClz()
接收的參數(shù)params
就是被裝飾的類HttpClient
- 為
HttpClient
動(dòng)態(tài)擴(kuò)展屬性屬性和方法
function logClz(params:any) { params.prototype.url = 'xxxx'; params.prototype.run = function() { console.log('run...'); }; } var http:any = new HttpClient(); http.run(); // run...
-
- 裝飾器工廠:閉包,返回的函數(shù)才是真正的裝飾器。
function logClz(params:string) { console.log('params:', params); //params: hello return function(target:any) { console.log('target:', target); //target: class HttpClient target.prototype.url = params; //擴(kuò)展一個(gè)url屬性 } } @logClz('hello') class HttpClient { constructor() { } } var http:any = new HttpClient(); console.log(http.url); //hello
- 在使用裝飾器工廠時(shí),如果不想給裝飾器傳參,可以把參數(shù)聲明為可選參數(shù),但使用裝飾器時(shí)仍然不能丟失小括號(hào)!
function logClz(params?:string) { return function(target:any) { } } @logClz() class HttpClient { constructor() { } }
- 重載構(gòu)造函數(shù)
- 類裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用,類的構(gòu)造函數(shù)作為其唯一的參數(shù);
- 如果類裝飾器返回一個(gè)值,它會(huì)使用提供的構(gòu)造函數(shù)來(lái)替換類的聲明;
function logClz(target:any) { return class extends target { url = 'change url' getData() { console.log('getData:', this.url); } } } @logClz class HttpClient { public url:string|undefined; constructor() { this.url = 'init url' } getData() { console.log(this.url); } } var http = new HttpClient(); //裝飾器返回的就是HttpClient的子類,因此TS可以自動(dòng)推導(dǎo) http 的類型 http.getData(); //getData: change url
- 修改類的定義
function fn<T extends {new(...args: any[]): {}}>(constructor: T): T { class Ps extends constructor { age: number = 20; //擴(kuò)展一個(gè)類型為number的屬性age } return Ps; } @fn class Person{ } let p:any = new Person(); //裝飾之后的Person已經(jīng)變成了Ps console.log(p.age) //20
function fn(v: number) { return function<T extends {new(...args: any[]): {}}>(cst: T): T { class Ps extends cst { age: number = v; } } } @fn(10) class Person { } //age:number = 10 @fn(20) class Cat { } //age:number = 20
T extends {new(...args: any[]): {}}:{new(...args: any[]): {}}
是對(duì)象字面量,等效于new(...args: any[]) => {}
,意思是一個(gè)能new
的函數(shù),返回值類型是{}
function identity<T>(arg: T): T { return arg; } let myIdentity: <U>(arg: U) => U = identity; # 等效: let myIdentity: {<T>(arg: T): T} = identity; # 轉(zhuǎn)換成接口: interface GenericIdentityFn { <T>(arg: T): T; } let myIdentity: GenericIdentityFn = identity;
屬性裝飾器
- 屬性裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用,傳入兩個(gè)參數(shù):
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員來(lái)說(shuō)是類的原型對(duì)象;
- 成員的名字;
function logProp(params:any) { return function(target:any, attr:any) { console.log(target) // { constructor:f, getData:f } console.log(attr) // url target[attr] = params; //通過(guò)原型對(duì)象修改屬性值 = 裝飾器傳入的參數(shù) target.api = 'xxxxx'; //擴(kuò)展屬性 target.run = function() { //擴(kuò)展方法 console.log('run...'); } } } class HttpClient { @logProp('http://baidu.com') public url:any|undefined; constructor() { } getData() { console.log(this.url); } } var http:any = new HttpClient(); http.getData(); // http://baidu.com console.log(http.api); // xxxxx http.run(); // run...
方法裝飾器
- 方法裝飾器被應(yīng)用到方法的屬性描述符上,可以用來(lái)監(jiān)視、修改、替換方法的定義;
- 方法裝飾器會(huì)在運(yùn)行時(shí)傳入3個(gè)參數(shù):
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員來(lái)說(shuō)是類的原型對(duì)象;
- 成員的名字;
- 成員的屬性描述符;
function get(params:any) { console.log(params) // 裝飾器傳入的參數(shù):http://baidu.com return function(target:any, methodName:any, desc:any) { console.log(target) // { constructor:f, getData:f } console.log(methodName) // getData console.log(desc) // {value: ?, writable: true, enumerable: false, configurable: true} value就是方法體 /* 修改被裝飾的方法 */ //1. 保存原方法體 var oldMethod = desc.value; //2. 重新定義方法體 desc.value = function(...args:any[]) { //3. 把傳入的數(shù)組元素都轉(zhuǎn)為字符串 let newArgs = args.map((item)=>{ return String(item); }); //4. 執(zhí)行原來(lái)的方法體 oldMethod.apply(this, newArgs); // 等效于 oldMethod.call(this, ...newArgs); } } } class HttpClient { constructor() { } @get('http://baidu.com') getData(...args:any[]) { console.log('getData: ', args); } } var http = new HttpClient(); http.getData(1, 2, true); // getData: ["1", "2", "true"]
方法參數(shù)裝飾器
- 參數(shù)裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)被調(diào)用,可以為類的原型增加一些元素?cái)?shù)據(jù),傳入3個(gè)參數(shù):
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員來(lái)說(shuō)是類的原型對(duì)象;
-
方法名稱,如果裝飾的是構(gòu)造函數(shù)的參數(shù),則值為
undefined
- 參數(shù)在函數(shù)參數(shù)列表中的索引;
function logParams(params:any) { console.log(params) // 裝飾器傳入的參數(shù):uuid return function(target:any, methodName:any, paramIndex:any) { console.log(target) // { constructor:f, getData:f } console.log(methodName) // getData console.log(paramIndex) // 0 } } class HttpClient { constructor() { } getData(@logParams('uuid') uuid:any) { console.log(uuid); } }
- 注意:參數(shù)裝飾器只能用來(lái)監(jiān)視一個(gè)方法的參數(shù)是否被傳入;
- 參數(shù)裝飾器在
Angular
中被廣泛使用,特別是結(jié)合reflect-metadata
庫(kù)來(lái)支持實(shí)驗(yàn)性的Metadata API
; - 參數(shù)裝飾器的返回值會(huì)被忽略。
裝飾器的執(zhí)行順序
- 裝飾器組合:TS支持多個(gè)裝飾器同時(shí)裝飾到一個(gè)聲明上,語(yǔ)法支持從左到右,或從上到下書寫;
@f @g x @f @g x
- 在TypeScript里,當(dāng)多個(gè)裝飾器應(yīng)用在一個(gè)聲明上時(shí)會(huì)進(jìn)行如下步驟的操作:
- 由上至下依次對(duì)裝飾器表達(dá)式求值;
- 求值的結(jié)果會(huì)被當(dāng)作函數(shù),由下至上依次調(diào)用.
- 不同裝飾器的執(zhí)行順序:屬性裝飾器 > 方法裝飾器 > 參數(shù)裝飾器 > 類裝飾器
function logClz11(params:string) { return function(target:any) { console.log('logClz11') } } function logClz22(params?:string) { return function(target:any) { console.log('logClz22') } } function logAttr(params?:string) { return function(target:any, attrName:any) { console.log('logAttr') } } function logMethod(params?:string) { return function(target:any, methodName:any, desc:any) { console.log('logMethod') } } function logParam11(params?:any) { return function(target:any, methodName:any, paramIndex:any) { console.log('logParam11') } } function logParam22(params?:any) { return function(target:any, methodName:any, paramIndex:any) { console.log('logParam22') } } @logClz11('http://baidu.com') @logClz22() class HttpClient { @logAttr() public url:string|undefined; constructor() { } @logMethod() getData() { console.log('get data'); } setData(@logParam11() param1:any, @logParam22() param2:any) { console.log('set data'); } } // logAttr --> logMethod --> logParam22 --> logParam11 --> logClz22 --> logClz11