1. 程式人生 > 程式設計 >TypeScript 裝飾器定義

TypeScript 裝飾器定義

目錄
  • 1.概念
    • 1.1定義
    • 1.2裝飾器工廠
    • 1.3裝飾器組合使用
    • 1.4裝飾器求值
  • 2.類裝飾器
    • 3.方法裝飾器
      • 4.訪問器裝飾器
        • 5.屬性裝飾器
          • 6.引數裝飾器

            前言:

            裝飾器Decorator ECMAScript中已經提案,但是目前還沒有定案;在TypeScript中已經將其實現,但是這仍是一項正在試驗中的特性,如果想要使用裝飾器,需要在tsconfig.on中將experimentalDecorators屬性,將其設定為true。

            1.概念

            1.1定義

            裝飾器是一種新的宣告,它可以作用於類宣告 、方法 、訪問器 、屬性 以及引數 上。裝飾器的使用採用@符號加一個函式名稱,例如@testDecorator

            ,其中,這個testDecorator必須是一個函式或者 return一個函式 ,這個函式在執行的時候被呼叫,被裝飾的宣告作為引數會自動傳入。

            值得注意的是 ,裝飾器要緊挨著要修飾的內容的前面 ,而且所有的裝飾器不能用在宣告檔案.d.ts.中,和任何外部上下文中(比如declare)。

            裝飾器的定義以及使用如下所示:

            // 定義一個函式作為裝飾器函式使用
            function testDecorator() {}
            
            // 通過@符號使用裝飾器
            @testDecorator
            
            

            1.2裝飾器工廠

            所謂的裝飾器工廠也是一個函式,與普通的裝飾器函式不同的是它的返回值是一個函式,返回的函式作為裝飾器呼叫的函式。如果使用裝飾器工廠,可以在使用的時候根據當前的使用情況,傳遞不同的引數,但是在使用的時候,就需要加上函式呼叫。

            示例程式碼如下:

            // 裝飾器工廠,返回值是一個函式
            function testDecorator() {
                return function() {}
            }
            
            // 通過@符號 + 函式呼叫的方式使用裝飾器
            @testDecorator()
            
            

            1.3裝飾器組合使用

            裝飾器是可以組合使用的,也就是說可以對用一個目標,引用多個裝飾器,

            示例程式碼如下所示:

            // 定義兩個裝飾器函式
            function setName() {}
            function setAge() {}
            
            // 使用裝飾器
            @setName
            @setAge
            class Person {}
            
            

            如果使用多個裝飾器,裝飾器的執行是有順序的,執行順序如下:

            如果使用的普通的裝飾器函式的話,執行順序是從下往上執行的,

            示例程式碼如下:

            function setName(constructor: any) {
              console.log('setName',constructor)
            }
            function setAge(constructor: any) {
              console.log('setAge',constructor)
            }
            @setName
            @setAge
            class Person {}
            /* 執行結果如下:
            setAge [Function: Person]
            setName [Function: Person]
            */
            
            
            

            如果是裝飾器工廠的,它的執行順序是先從上到下依次執行工廠函式,然後從下往上依次執行工廠函式return的函式。示例程式碼如下

            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 {}
            /* 執行結果如下:
            get setName
            get setAge
            setAge [Function: Person]
            setName [Function: Person]
            */
            
            

            1.4裝飾器求值

            類的定義中不同宣告上的裝飾器將按以下規定的順序引用:

            • 引數裝飾器,方法裝飾器,訪問符裝飾器或屬性裝飾器應用到每個例項成員;
            • 引數裝飾器,方法裝飾器,訪問符裝飾器或屬性裝飾器應用到每個靜態成員;
            • 引數裝飾器應用到建構函式;
            • 類裝飾器應用到類。

            2.類裝飾器

            類裝飾器 在類宣告之前使用,必須緊挨著需要裝飾的內容,類裝飾器應用於類的宣告。

            類裝飾器表示式會在執行時當做函式被呼叫,它有一個引數,就是這個類的建構函式。

            示例程式碼如下:

            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本身。

            我們還可以通過裝飾器來修改類的原型物件和建構函式,示例程式碼如下:

            // * 通過裝飾器 修改原型物件與建構函式
            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這個屬性,但是實際上並不是這樣的,這裡已經丟擲一個異常,想要解決這個問題,可以通過型別斷言的方式,也可以通過定義一個同名介面,通過宣告合併的方式解決這個問題。

            示例程式碼如下:

            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) // 一碗周
            
            

            而且我們還可以通過裝飾器過載建構函式,示例程式碼如下:

            // * 過載建構函式
            function classDecorator<T extends { new (...args: any[]): {} }>(
              construcwww.cppcns.comtor: 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)
            /* 執行結果如下:
            {
              age: 18,name: '一碗周',hobby: 'coding',}
            */
            
            

            我們還可以通過裝飾器工廠的方式來傳遞參數,示例程式碼如下:

            // 定義一個裝飾器工廠
            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)
            /* 執行結果如下:
            {
              age: 18,}
            */
            
            

            3.方法裝飾器

            方法裝飾器用來處理類中的方法,它可以處理方法的屬性描述符(關於什麼是屬性描述符,請參考Object.defineProperty() ),也可以處理方法定義。方法裝飾器在執行時也是被當做函式呼叫,其包含三個引數,

            具體如下所示:

            對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。

            成員的名字。

            成員的屬性描述符 。

            值得注意的是如果程式碼輸出目標版本小於ES5,屬性描述符 將會是undefined

            如下程式碼通過裝飾器工廠定義了一個簡單的方法裝飾器,示例程式碼如下:

            // 裝飾器工廠
            function enumerable(bool: boolean) {
              /**
               * 方法裝飾器接受三個引數:
               * 1. target:對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件
               * 2. propertyName:成員的名字
               * 3. descriptor:屬性描述符,其型別為 PropertyDescriptor
               */
              return function (
                target: any,propertyName: string,descriptor: PropertyDescriptor,) {
                // 根據傳入的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,) {
                return {
                  value: function () {
                    return 'Error: name is undefined'
                  },enumerable: bool,}
              }
            }
            class Info {
              construHgRNIFCVBctor(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.訪問器裝飾器

            訪問器裝飾器就是之前所學習的setget方法,一個在設定屬性值的時候觸發,一個在獲取屬性值的時候觸發。

            訪問器裝飾器同樣也接受三個引數,與方法裝飾器一樣,這裡不做贅述了,

            示例程式碼如下:

            function enumerable(bool: boolean) {
              return function (
                target: any,) {
                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不允許同時裝飾一個成員的getset訪問器。

            5.屬性裝飾器

            屬性裝飾器宣告在屬性宣告之前,它有兩個引數,如下所示:

            • 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。
            • 成員的名字。

            示例程式碼如下:

            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)
            
            

            執行結果如下:

            name
            age

            6.引數裝飾器

            引數裝飾器具有三個引數,具體如下:

            • 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。
            • 成員的名字。
            • 引數在函式引數列表中的索引。

            引數裝飾器的作用是用於監視一個方法的引數是否被傳入,引數裝飾器的返回值會被忽略。

            示例程式碼如下:

            function required(target: any,index: number) {
              console.log(`修飾的是${propertyName}的第${index + 1}個引數`)
            }
            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個引數
            
            
            

            這裡我們在getInfo方法的第二個引數之前使用引數裝飾器,從而可以在裝飾器中獲取到一些資訊。

            到此這篇關於TypeScript 裝飾器定義的文章就介紹到這了,更多相關TypeScript 裝飾器內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!