TypeScript 裝飾器定義
前言:
裝飾器Decorator 在ECMAScript中已經(jīng)提案,但是目前還沒有定案;在TypeScript中已經(jīng)將其實現(xiàn),但是這仍是一項正在試驗中的特性,如果想要使用裝飾器,需要在tsconfig.json中將experimentalDecorators屬性,將其設置為true。
1.概念
1.1定義
裝飾器是一種新的聲明,它可以作用于類聲明 、方法 、訪問器 、屬性 以及參數(shù) 上。裝飾器的使用采用@符號加一個函數(shù)名稱,例如@testDecorator,其中,這個testDecorator必須是一個函數(shù)或者 return一個函數(shù) ,這個函數(shù)在運行的時候被調用,被裝飾的聲明作為參數(shù)會自動傳入。
值得注意的是 ,裝飾器要緊挨著要修飾的內(nèi)容的前面 ,而且所有的裝飾器不能用在聲明文件.d.ts.中,和任何外部上下文中(比如declare)。
裝飾器的定義以及使用如下所示:
// 定義一個函數(shù)作為裝飾器函數(shù)使用
function testDecorator() {}
// 通過@符號使用裝飾器
@testDecorator
1.2裝飾器工廠
所謂的裝飾器工廠也是一個函數(shù),與普通的裝飾器函數(shù)不同的是它的返回值是一個函數(shù),返回的函數(shù)作為裝飾器調用的函數(shù)。如果使用裝飾器工廠,可以在使用的時候根據(jù)當前的使用情況,傳遞不同的參數(shù),但是在使用的時候,就需要加上函數(shù)調用。
示例代碼如下:
// 裝飾器工廠,返回值是一個函數(shù)
function testDecorator() {
return function() {}
}
// 通過@符號 + 函數(shù)調用的方式使用裝飾器
@testDecorator()
1.3裝飾器組合使用
裝飾器是可以組合使用的,也就是說可以對用一個目標,引用多個裝飾器,
示例代碼如下所示:
// 定義兩個裝飾器函數(shù)
function setName() {}
function setAge() {}
// 使用裝飾器
@setName
@setAge
class Person {}
如果使用多個裝飾器,裝飾器的執(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í)行結果如下:
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í)行結果如下:
get setName
get setAge
setAge [Function: Person]
setName [Function: Person]
*/
1.4裝飾器求值
類的定義中不同聲明上的裝飾器將按以下規(guī)定的順序引用:
- 參數(shù)裝飾器,方法裝飾器,訪問符裝飾器或屬性裝飾器應用到每個實例成員;
- 參數(shù)裝飾器,方法裝飾器,訪問符裝飾器或屬性裝飾器應用到每個靜態(tài)成員;
- 參數(shù)裝飾器應用到構造函數(shù);
- 類裝飾器應用到類。
2.類裝飾器
類裝飾器 在類聲明之前使用,必須緊挨著需要裝飾的內(nèi)容,類裝飾器應用于類的聲明。
類裝飾器表達式會在運行時當做函數(shù)被調用,它有一個參數(shù),就是這個類的構造函數(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的原型對象的constructor屬性指向的其實就是Info本身。
我們還可以通過裝飾器來修改類的原型對象和構造函數(shù),示例代碼如下:
// * 通過裝飾器 修改原型對象與構造函數(shù)
function addName(constructor: { new (): any }) {
constructor.prototype.name = '一碗周'
}
@addName
class Person {}
const person = new Person()
console.log(person.name) // error 類型“A”上不存在屬性“name”
在上面的代碼中,我們通過addName修飾符在類Person的原型上添加一個name屬性,這樣使得通過Person類實例化的對象,都可以訪問name這個屬性,但是實際上并不是這樣的,這里已經(jīng)拋出一個異常,想要解決這個問題,可以通過類型斷言的方式,也可以通過定義一個同名接口,通過聲明合并的方式解決這個問題。
示例代碼如下:
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) // 一碗周
而且我們還可以通過裝飾器重載構造函數(shù),示例代碼如下:
// * 重載構造函數(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í)行結果如下:
{
age: 18,
name: '一碗周',
hobby: 'coding',
}
*/
我們還可以通過裝飾器工廠的方式來傳遞參數(shù),示例代碼如下:
// 定義一個裝飾器工廠
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í)行結果如下:
{
age: 18,
name: '一碗周',
hobby: 'coding',
}
*/
3.方法裝飾器
方法裝飾器用來處理類中的方法,它可以處理方法的屬性描述符(關于什么是屬性描述符,請參考Object.defineProperty() ),也可以處理方法定義。方法裝飾器在運行時也是被當做函數(shù)調用,其包含三個參數(shù),
具體如下所示:
對于靜態(tài)成員來說是類的構造函數(shù),對于實例成員是類的原型對象。
成員的名字。
成員的屬性描述符 。
值得注意的是如果代碼輸出目標版本小于ES5,屬性描述符 將會是undefined。
如下代碼通過裝飾器工廠定義了一個簡單的方法裝飾器,示例代碼如下:
// 裝飾器工廠
function enumerable(bool: boolean) {
/**
* 方法裝飾器接受三個參數(shù):
* 1. target:對于靜態(tài)成員來說是類的構造函數(shù),對于實例成員是類的原型對象
* 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('一碗周')
// 如果直接打印,該對象中不包含 getName() 方法,因為該方法是不可枚舉的。
console.log(info) // { name: '一碗周' }
// 但是可以調用該方法
console.log(info.getName()) // 一碗周
在上面的代碼中,我們直接通過裝飾器對類中的方法的屬性描述符進行了修改。
如果方法裝飾器返回一個值,那么會用這個值作為方法的屬性描述符對象,示例代碼如下:
// 裝飾器工廠
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
在上面的代碼中,我們的方法裝飾器中返回了一個對象,該對象的value屬性修改了方法的定義,所以最終看到的結果為Error: name is undefined。
4.訪問器裝飾器
訪問器裝飾器就是之前所學習的set和get方法,一個在設置屬性值的時候觸發(fā),一個在獲取屬性值的時候觸發(fā)。
訪問器裝飾器同樣也接受三個參數(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不允許同時裝飾一個成員的get和set訪問器。
5.屬性裝飾器
屬性裝飾器聲明在屬性聲明之前,它有兩個參數(shù),如下所示:
- 對于靜態(tài)成員來說是類的構造函數(shù),對于實例成員是類的原型對象。
- 成員的名字。
示例代碼如下:
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í)行結果如下:
name
age
6.參數(shù)裝飾器
參數(shù)裝飾器具有三個參數(shù),具體如下:
- 對于靜態(tài)成員來說是類的構造函數(shù),對于實例成員是類的原型對象。
- 成員的名字。
- 參數(shù)在函數(shù)參數(shù)列表中的索引。
參數(shù)裝飾器的作用是用于監(jiān)視一個方法的參數(shù)是否被傳入,參數(shù)裝飾器的返回值會被忽略。
示例代碼如下:
function required(target: any, propertyName: string, index: number) {
console.log(`修飾的是${propertyName}的第${index + 1}個參數(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個參數(shù)
這里我們在getInfo方法的第二個參數(shù)之前使用參數(shù)裝飾器,從而可以在裝飾器中獲取到一些信息。
到此這篇關于TypeScript 裝飾器定義的文章就介紹到這了,更多相關TypeScript 裝飾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Layer UI表格列日期格式化及取消自動填充日期的實現(xiàn)方法
這篇文章主要介紹了Layer UI表格列日期格式化及取消自動填充日期的實現(xiàn)方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05
關于BootStrap modal 在IOS9中不能彈出的解決方法(IOS 9 bootstrap modal ios
本文給大家介紹BootStrap modal 在IOS9中不能彈出的問題以及bootstrap datepicker 在bootstrap modal中不顯示問題的解決方案,非常不錯,需要的朋友參考下2016-12-12
javascript實現(xiàn)dom動態(tài)創(chuàng)建省市縱向列表菜單的方法
這篇文章主要介紹了javascript實現(xiàn)dom動態(tài)創(chuàng)建省市縱向列表菜單的方法,可實現(xiàn)省市列表菜單效果,涉及javascript鼠標事件及頁面處理json數(shù)據(jù)的技巧,需要的朋友可以參考下2015-05-05
JavaScript實現(xiàn)鼠標滑過處生成氣泡的方法
這篇文章主要介紹了JavaScript實現(xiàn)鼠標滑過處生成氣泡的方法,涉及鼠標事件與頁面樣式的相關操作技巧,需要的朋友可以參考下2015-05-05

