TypeScript 裝飾器定義
前言:
裝飾器Decorator
在ECMAScript
中已經(jīng)提案,但是目前還沒(méi)有定案;在TypeScript中已經(jīng)將其實(shí)現(xiàn),但是這仍是一項(xiàng)正在試驗(yàn)中的特性,如果想要使用裝飾器,需要在tsconfig.json
中將experimentalDecorators
屬性,將其設(shè)置為true。
1.概念
1.1定義
裝飾器是一種新的聲明,它可以作用于類聲明 、方法 、訪問(wèn)器 、屬性 以及參數(shù) 上。裝飾器的使用采用@符號(hào)加一個(gè)函數(shù)名稱,例如@testDecorator
,其中,這個(gè)testDecorator
必須是一個(gè)函數(shù)或者 return一個(gè)函數(shù) ,這個(gè)函數(shù)在運(yùn)行的時(shí)候被調(diào)用,被裝飾的聲明作為參數(shù)會(huì)自動(dòng)傳入。
值得注意的是 ,裝飾器要緊挨著要修飾的內(nèi)容的前面 ,而且所有的裝飾器不能用在聲明文件.d.ts.中,和任何外部上下文中(比如declare
)。
裝飾器的定義以及使用如下所示:
// 定義一個(gè)函數(shù)作為裝飾器函數(shù)使用 function testDecorator() {} // 通過(guò)@符號(hào)使用裝飾器 @testDecorator
1.2裝飾器工廠
所謂的裝飾器工廠也是一個(gè)函數(shù),與普通的裝飾器函數(shù)不同的是它的返回值是一個(gè)函數(shù),返回的函數(shù)作為裝飾器調(diào)用的函數(shù)。如果使用裝飾器工廠,可以在使用的時(shí)候根據(jù)當(dāng)前的使用情況,傳遞不同的參數(shù),但是在使用的時(shí)候,就需要加上函數(shù)調(diào)用。
示例代碼如下:
// 裝飾器工廠,返回值是一個(gè)函數(shù) function testDecorator() { return function() {} } // 通過(guò)@符號(hào) + 函數(shù)調(diào)用的方式使用裝飾器 @testDecorator()
1.3裝飾器組合使用
裝飾器是可以組合使用的,也就是說(shuō)可以對(duì)用一個(gè)目標(biāo),引用多個(gè)裝飾器,
示例代碼如下所示:
// 定義兩個(gè)裝飾器函數(shù) function setName() {} function setAge() {} // 使用裝飾器 @setName @setAge class Person {}
如果使用多個(gè)裝飾器,裝飾器的執(zhí)行是有順序的,執(zhí)行順序如下:
如果使用的普通的裝飾器函數(shù)的話,執(zhí)行順序是從下往上執(zhí)行的,
示例代碼如下:
function setName(constructor: any) { console.log('setName', constructor) } function setAge(constructor: any) { console.log('setAge', constructor) } @setName @setAge class Person {} /* 執(zhí)行結(jié)果如下: setAge [Function: Person] setName [Function: Person] */
如果是裝飾器工廠的,它的執(zhí)行順序是先從上到下依次執(zhí)行工廠函數(shù),然后從下往上依次執(zhí)行工廠函數(shù)return的函數(shù)。示例代碼如下
function setName() { console.log('get setName') return function (constructor: any) { console.log('setName', constructor) } } function setAge() { console.log('get setAge') return function (constructor: any) { console.log('setAge', constructor) } } @setName() @setAge() class Person {} /* 執(zhí)行結(jié)果如下: get setName get setAge setAge [Function: Person] setName [Function: Person] */
1.4裝飾器求值
類的定義中不同聲明上的裝飾器將按以下規(guī)定的順序引用:
- 參數(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ù);
- 類裝飾器應(yīng)用到類。
2.類裝飾器
類裝飾器 在類聲明之前使用,必須緊挨著需要裝飾的內(nèi)容,類裝飾器應(yīng)用于類的聲明。
類裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)做函數(shù)被調(diào)用,它有一個(gè)參數(shù),就是這個(gè)類的構(gòu)造函數(shù)。
示例代碼如下:
let sign = null function setName() { return function (constructor: Function) { sign = constructor } } @setName() class Info { constructor() {} } console.log(sign === Info) // true console.log(sign === Info.prototype.constructor) // true
如上代碼可以知道類Info
的原型對(duì)象的constructor
屬性指向的其實(shí)就是Info
本身。
我們還可以通過(guò)裝飾器來(lái)修改類的原型對(duì)象和構(gòu)造函數(shù),示例代碼如下:
// * 通過(guò)裝飾器 修改原型對(duì)象與構(gòu)造函數(shù) function addName(constructor: { new (): any }) { constructor.prototype.name = '一碗周' } @addName class Person {} const person = new Person() console.log(person.name) // error 類型“A”上不存在屬性“name”
在上面的代碼中,我們通過(guò)addName
修飾符在類Person
的原型上添加一個(gè)name屬性,這樣使得通過(guò)Person類實(shí)例化的對(duì)象,都可以訪問(wèn)name這個(gè)屬性,但是實(shí)際上并不是這樣的,這里已經(jīng)拋出一個(gè)異常,想要解決這個(gè)問(wèn)題,可以通過(guò)類型斷言的方式,也可以通過(guò)定義一個(gè)同名接口,通過(guò)聲明合并的方式解決這個(gè)問(wèn)題。
示例代碼如下:
function addName(constructor: { new (): any }) { constructor.prototype.name = '一碗周' } @addName class Person {} const person = new Person() // 1. 類型斷言 // console.log((person as any).name) // 一碗周 // 2. 定義同名接口,聲明合并 interface Person { name: string } console.log(person.name) // 一碗周
而且我們還可以通過(guò)裝飾器重載構(gòu)造函數(shù),示例代碼如下:
// * 重載構(gòu)造函數(shù) function classDecorator<T extends { new (...args: any[]): {} }>( constructor: T, ) { return class extends constructor { name = '一碗周' hobby = 'coding' } } @classDecorator class Person { age = 18 name: string constructor(name: string) { this.name = name } } const person = new Person('一碗周') console.log(person) /* 執(zhí)行結(jié)果如下: { age: 18, name: '一碗周', hobby: 'coding', } */
我們還可以通過(guò)裝飾器工廠的方式來(lái)傳遞參數(shù),示例代碼如下:
// 定義一個(gè)裝飾器工廠 function classDecorator(_name: string) { return function <T extends { new (...args: any[]): {} }>(constructor: T) { return class extends constructor { name = _name hobby = 'coding' } } } @classDecorator('一碗周') class Person { age = 18 name: string constructor(name: string) { this.name = name } } const person = new Person('一碗粥') console.log(person) /* 執(zhí)行結(jié)果如下: { age: 18, name: '一碗周', hobby: 'coding', } */
3.方法裝飾器
方法裝飾器用來(lái)處理類中的方法,它可以處理方法的屬性描述符(關(guān)于什么是屬性描述符,請(qǐng)參考Object.defineProperty() ),也可以處理方法定義。方法裝飾器在運(yùn)行時(shí)也是被當(dāng)做函數(shù)調(diào)用,其包含三個(gè)參數(shù),
具體如下所示:
對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象。
成員的名字。
成員的屬性描述符 。
值得注意的是如果代碼輸出目標(biāo)版本小于ES5,屬性描述符 將會(huì)是undefined
。
如下代碼通過(guò)裝飾器工廠定義了一個(gè)簡(jiǎn)單的方法裝飾器,示例代碼如下:
// 裝飾器工廠 function enumerable(bool: boolean) { /** * 方法裝飾器接受三個(gè)參數(shù): * 1. target:對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象 * 2. propertyName:成員的名字 * 3. descriptor:屬性描述符,其類型為 PropertyDescriptor */ return function ( target: any, propertyName: string, descriptor: PropertyDescriptor, ) { // 根據(jù)傳入的bool決定該方法是否可枚舉 descriptor.enumerable = bool } } class Info { constructor(public name: string) {} @enumerable(false) getName() { return this.name } } const info = new Info('一碗周') // 如果直接打印,該對(duì)象中不包含 getName() 方法,因?yàn)樵摲椒ㄊ遣豢擅杜e的。 console.log(info) // { name: '一碗周' } // 但是可以調(diào)用該方法 console.log(info.getName()) // 一碗周
在上面的代碼中,我們直接通過(guò)裝飾器對(duì)類中的方法的屬性描述符進(jìn)行了修改。
如果方法裝飾器返回一個(gè)值,那么會(huì)用這個(gè)值作為方法的屬性描述符對(duì)象,示例代碼如下:
// 裝飾器工廠 function enumerable(bool: boolean) { return function ( target: any, propertyName: string, descriptor: PropertyDescriptor, ) { return { value: function () { return 'Error: name is undefined' }, enumerable: bool, } } } class Info { constructor(public name: string) {} @enumerable(false) getName() { return this.name } } const info = new Info('一碗周') console.log(info) // { name: '一碗周' } console.log(info.getName()) // Error: name is undefined
在上面的代碼中,我們的方法裝飾器中返回了一個(gè)對(duì)象,該對(duì)象的value屬性修改了方法的定義,所以最終看到的結(jié)果為Error: name is undefined
。
4.訪問(wèn)器裝飾器
訪問(wèn)器裝飾器就是之前所學(xué)習(xí)的set
和get
方法,一個(gè)在設(shè)置屬性值的時(shí)候觸發(fā),一個(gè)在獲取屬性值的時(shí)候觸發(fā)。
訪問(wèn)器裝飾器同樣也接受三個(gè)參數(shù),與方法裝飾器一樣,這里不做贅述了,
示例代碼如下:
function enumerable(bool: boolean) { return function ( target: any, propertyName: string, descriptor: PropertyDescriptor, ) { descriptor.enumerable = bool } } class Info { private _name: string constructor(name: string) { this._name = name } @enumerable(false) get name() { return this._name } set name(name) { this._name = name } }
值得注意的是,在TypeScript
不允許同時(shí)裝飾一個(gè)成員的get
和set
訪問(wèn)器。
5.屬性裝飾器
屬性裝飾器聲明在屬性聲明之前,它有兩個(gè)參數(shù),如下所示:
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象。
- 成員的名字。
示例代碼如下:
function printPropertyName(target: any, propertyName: string) { console.log(propertyName) } class Info { @printPropertyName name: string @printPropertyName age: number constructor(name: string, age: number) { this.name = name this.age = age } } new Info('一碗周', 18)
執(zhí)行結(jié)果如下:
name
age
6.參數(shù)裝飾器
參數(shù)裝飾器具有三個(gè)參數(shù),具體如下:
- 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象。
- 成員的名字。
- 參數(shù)在函數(shù)參數(shù)列表中的索引。
參數(shù)裝飾器的作用是用于監(jiān)視一個(gè)方法的參數(shù)是否被傳入,參數(shù)裝飾器的返回值會(huì)被忽略。
示例代碼如下:
function required(target: any, propertyName: string, index: number) { console.log(`修飾的是${propertyName}的第${index + 1}個(gè)參數(shù)`) } class Info { name: string = '一碗周' age: number = 18 getInfo(prefix: string, @required infoType: string): any { return prefix + ' ' + this[infoType] } } interface Info { [key: string]: string | number | Function } const info = new Info() info.getInfo('', 'age') // 修飾的是getInfo的第2個(gè)參數(shù)
這里我們?cè)?code>getInfo方法的第二個(gè)參數(shù)之前使用參數(shù)裝飾器,從而可以在裝飾器中獲取到一些信息。
到此這篇關(guān)于TypeScript 裝飾器定義的文章就介紹到這了,更多相關(guān)TypeScript 裝飾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js導(dǎo)出Excel表格超出26位英文字符的解決方法ES6
下面小編就為大家?guī)?lái)一篇js導(dǎo)出Excel表格超出26位英文字符的解決方法ES6。具有很好的參考價(jià)值。一起跟隨小編過(guò)來(lái)看看吧,希望對(duì)大家有所幫助2017-11-11HTML5開發(fā)Kinect體感游戲的實(shí)例應(yīng)用
這篇文章主要介紹了HTML5開發(fā)Kinect體感游戲的實(shí)例應(yīng)用的相關(guān)資料,希望通過(guò)本文能夠幫助到大家,需要的朋友可以參考下2017-09-09Layer UI表格列日期格式化及取消自動(dòng)填充日期的實(shí)現(xiàn)方法
這篇文章主要介紹了Layer UI表格列日期格式化及取消自動(dòng)填充日期的實(shí)現(xiàn)方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05關(guān)于BootStrap modal 在IOS9中不能彈出的解決方法(IOS 9 bootstrap modal ios
本文給大家介紹BootStrap modal 在IOS9中不能彈出的問(wèn)題以及bootstrap datepicker 在bootstrap modal中不顯示問(wèn)題的解決方案,非常不錯(cuò),需要的朋友參考下2016-12-12javascript實(shí)現(xiàn)dom動(dòng)態(tài)創(chuàng)建省市縱向列表菜單的方法
這篇文章主要介紹了javascript實(shí)現(xiàn)dom動(dòng)態(tài)創(chuàng)建省市縱向列表菜單的方法,可實(shí)現(xiàn)省市列表菜單效果,涉及javascript鼠標(biāo)事件及頁(yè)面處理json數(shù)據(jù)的技巧,需要的朋友可以參考下2015-05-05es6數(shù)組includes()用法實(shí)例分析
這篇文章主要介紹了es6數(shù)組includes()用法,結(jié)合實(shí)例形式分析了es6數(shù)組includes()針對(duì)給定值判斷的相關(guān)操作技巧與使用注意事項(xiàng),需要的朋友可以參考下2020-04-04JavaScript實(shí)現(xiàn)鼠標(biāo)滑過(guò)處生成氣泡的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)鼠標(biāo)滑過(guò)處生成氣泡的方法,涉及鼠標(biāo)事件與頁(yè)面樣式的相關(guān)操作技巧,需要的朋友可以參考下2015-05-05javascript實(shí)現(xiàn)的簡(jiǎn)單計(jì)時(shí)器
計(jì)時(shí)器提供了一 個(gè)可以將代碼片段異步延時(shí)執(zhí)行的能力,javascript生來(lái)是單線程的(在一定時(shí)間范圍內(nèi)僅一部分js代碼能運(yùn)行),計(jì)時(shí)器為我們提供了一種避開這種 限制的方法,從而開辟了另一條執(zhí)行代碼的蹊徑。2015-07-07