自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

探索TypeScript:裝飾器

開發(fā) 前端
函數(shù)最后都會返回r?對象,一開始會給予實(shí)參個(gè)數(shù)以及特定參數(shù)進(jìn)行判斷處理,然后基于decorators、target獲得所有裝飾方法,然后拿到裝飾類的原型。

前言

最近在學(xué)習(xí)Nest.js的內(nèi)容,發(fā)現(xiàn)裝飾器本質(zhì)和Java的面向切面編程。裝飾器用于給類,方法,屬性以及方法參數(shù)等增加一些附屬功能而不影響其原有特性。其在Typescript應(yīng)用中的主要作用類似于Java中的注解,在AOP(面向切面編程)使用場景下非常有用。

面向切面編程(AOP)  是一種編程范式,它允許我們分離橫切關(guān)注點(diǎn),藉此達(dá)到增加模塊化程度的目標(biāo)。它可以在不修改代碼自身的前提下,給已有代碼增加額外的行為(通知)

裝飾器一般用于處理一些與類以及類屬性本身無關(guān)的邏輯,例如: 一個(gè)類方法的執(zhí)行耗時(shí)統(tǒng)計(jì)或者記錄日志,可以單獨(dú)拿出來寫成裝飾器。

看一下官方的解釋更加清晰明了

裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明,方法, 訪問符,屬性或參數(shù)上。 裝飾器使用 @expression這種形式,expression求值后必須為一個(gè)函數(shù),它會在運(yùn)行時(shí)被調(diào)用,被裝飾的聲明信息做為參數(shù)傳入。

如果有使用過spring boot或者php的symfony框架的話,就基本知道裝飾器的作用分別類似于以上兩者注解和annotation,而node中裝飾器用的比較好的框架是nest.js。不過不了解也沒關(guān)系,接下來我就按我的理解講解一下裝飾器的使用。

不過目前裝飾器還不屬于標(biāo)準(zhǔn),還在建議征集的第二階段,但這并不妨礙我們在ts中的使用。只要在 tsconfig.json中開啟 experimentalDecorators就可以使用了。

{  
    "compilerOptions": {  
        "target": "ES5",  
        "experimentalDecorators": true  
    }  
}

類裝飾器

類裝飾器僅接受一個(gè)參數(shù),該參數(shù)表示類本身。

同時(shí),如果類裝飾器返回一個(gè)值,它會使用提供的構(gòu)造函數(shù)來替換類的聲明。

比如:

// 類裝飾器,接受一個(gè)參數(shù)即為類本身
// 將裝飾后的類以及類的原型全部凍結(jié)變?yōu)椴豢蓴U(kuò)展以及不可修改
function freeze(constructor: Function) {
  Object.freeze(constructor); // 凍結(jié)裝飾的類
  Object.freeze(constructor.prototype); // 凍結(jié)類的原型
}


// 調(diào)用 freeze 裝飾裝飾 BugReport
@freeze
class BugReport {
  static type = 'report'
}


BugReport.type = 'hello'
console.log(BugReport.type) // TypeError: Cannot assign to read only property 'type' of function 'class BugReport

同時(shí)類裝飾器如果存在一個(gè)有效返回值,該返回值會替代被修飾類的構(gòu)造函數(shù)返回的實(shí)例對象。比如:

function override(target: new () => any) {
  return class Child {

  }
}

@override // override 裝飾器修改了 Parent class 返回的實(shí)例對象
class Parent {

}

const instance = new Parent()

console.log(instance) // Child {}

方法裝飾器

方法裝飾器是在方法聲明之前聲明的。方式裝飾器可用于觀察、修改或替換方法定義。

方法裝飾器接受三個(gè)參數(shù):

  • 如果該裝飾器修飾的是類的靜態(tài)方法,那么第一個(gè)參數(shù)表示當(dāng)前類的構(gòu)造函數(shù)(即當(dāng)前類)。如果修飾為類的原型方式,那么第一個(gè)參數(shù)表示該類的原型對象(prototype)。
  • 第二個(gè)參數(shù)表示該方法參數(shù)器修改的類的名稱。
  • 第三個(gè)參數(shù)表示當(dāng)前方法的屬性描述符。

同時(shí),如果方法裝飾器返回一個(gè)值,它會被用作方法的屬性描述符。

比如下面的例子,我們使用方法裝飾器修改類的實(shí)例方法,將 greet 方法變?yōu)椴豢擅杜e:

function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {

    console.log(target) // Greeter.prototype
    console.log(propertyKey) // greet

    // 將該方法(Greeter.prototype.greet) 變?yōu)椴豢擅杜e
    descriptor.enumerable = value;
  };
}


class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
 
  // @enumerable(false) 修飾實(shí)例方法,既修飾器第一個(gè)參數(shù)為 Greeter.prototype
  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}

console.log(Object.keys(Greeter.prototype)) // []

屬性訪問器裝飾器

屬性訪問器裝飾器同樣在屬性訪問器聲明前使用,常用于觀察、修改或替換屬性訪問器的定義。

當(dāng)屬性裝飾器被調(diào)用時(shí),和方法裝飾器同樣會接受三個(gè)參數(shù),分別為:

  • 如果當(dāng)前屬性訪問器為類的靜態(tài)屬性訪問器,那么屬性訪問器修飾器接受的第一個(gè)參數(shù)則為當(dāng)前類的構(gòu)造函數(shù)。否則,如果修飾的為實(shí)例上的屬性訪問器,則第一個(gè)參數(shù)為類的原型。
  • 第二個(gè)參數(shù)為當(dāng)前被修飾的成員名稱。
  • 第三個(gè)參數(shù)為當(dāng)前被修飾的屬性描述符。

同樣,如果訪問器裝飾器返回一個(gè)值,它也會被用作方法的屬性描述符。

比如,當(dāng)我們使用裝飾器來修飾當(dāng)前類上的屬性訪問器時(shí):

function baseLog(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  // 觸發(fā)屬性訪問器時(shí)
  console.log(`Trigger getter(${target.name}/${propertyKey})`)
}

class Person {

  @baseLog
  static get username() {
    return '19Qingfeng'
  }
}

// Trigger getter(Person/username)
// 19Qingfeng
console.log(Person.username)

參數(shù)裝飾器

同樣,class 上每個(gè)方法的參數(shù)還存在參數(shù)修飾器。參數(shù)修飾器會為參數(shù)聲明之前,同樣具有三個(gè)參數(shù):

  • 當(dāng)參數(shù)修飾器修飾的所在方法為類的構(gòu)造函數(shù)/靜態(tài)方法時(shí),第一個(gè)參數(shù)表示類的構(gòu)造函數(shù)(類本身)。反之,當(dāng)參數(shù)修飾器修飾的參數(shù)所在的方法為實(shí)例方法時(shí),此時(shí)第一個(gè)參數(shù)代表類的原型。
  • 如果修飾的為類的靜態(tài)/實(shí)例方法時(shí),第二個(gè)參數(shù)為當(dāng)前參數(shù)修飾器所在方法的方法名。如果參數(shù)修飾器所在的方法為類的構(gòu)造函數(shù)參數(shù)修飾時(shí),此時(shí)第二個(gè)參數(shù)為 undefined。
  • 第三個(gè)參數(shù),表示當(dāng)前參數(shù)所在方法的位置索引。

我們依次來看看參數(shù)裝飾器分別裝飾類的構(gòu)造函數(shù)、類的靜態(tài)方法上的參數(shù)以及類的實(shí)例方法上的參數(shù)不同表現(xiàn):

參數(shù)修飾器所在方法為修飾類的構(gòu)造函數(shù):

class Person {

  constructor(@logger name: string) {

  }
}


function logger(target: any, methodName: string | undefined, index: number) {
  console.log(target) // [Function: Person]
  console.log(methodName) // undefined
  console.log(index) // 0
}

至此所有常見的類裝飾器都介紹完了,其實(shí)本質(zhì)的裝飾器函數(shù)入?yún)⒍际且恢碌模谝粋€(gè)參數(shù)是裝飾器所在的類名、第二個(gè)參數(shù)是裝飾參數(shù),接下來我們看一下裝飾器的實(shí)現(xiàn)原理。

實(shí)現(xiàn)原理

我們將一個(gè)包含很多裝飾器的類將ts代碼編譯成es5的打包結(jié)果如下:

// ....
// 屬性裝飾器
__decorate([propertyDecorators], Parent.prototype, 'company', undefined);
// 訪問器屬性裝飾器(原型)
__decorate([accessorDecorator], Parent.prototype, 'gender', null);
// 方法裝飾器 & 參數(shù)(實(shí)例方法)裝飾器
__decorate(
  [methodDecorator, __param(0, paramDecorator)],
  Parent.prototype,
  'getName',
  null
);
// 訪問器屬性裝飾器(實(shí)例)
__decorate([accessorDecorator], Parent, 'staticGender', null);
// 方法裝飾器(實(shí)例)
__decorate([methodDecorator], Parent, 'getStaticName', null);
// 類裝飾器 & 參數(shù)裝飾器(類的構(gòu)造函數(shù))
Parent = __decorate([logger, __param(0, paramDecorator)], Parent);
return Parent;

會發(fā)現(xiàn)所有裝飾器都在調(diào)用__decorate方法,并且不同的裝飾器,對于__decorate方法的入?yún)⒁彩峭ㄓ眯秃軓?qiáng)。

  • 第一個(gè)參數(shù)表示當(dāng)前修飾器個(gè)數(shù)的集合,這是一個(gè)數(shù)組。
  • 第二個(gè)參數(shù)表示當(dāng)前修飾器修飾的目標(biāo)(類的構(gòu)造函數(shù)或者類的原型),這一步在 TS 編譯后就已經(jīng)確定。
  • 第三個(gè)參數(shù)如果存在的話,表示當(dāng)前修飾器修飾對象的 key (這是一個(gè)字符串,可能為方法名、屬性名等)。
  • 第四個(gè)參數(shù)如果存在的話,為 null 或者為 undefined。

然后我們再看一下具體的__decorate方法:

var __decorate = function (decorators, target, key, desc) {
 // 首先獲得實(shí)參的個(gè)數(shù)
 var c = arguments.length,

 // 1. 如果實(shí)參個(gè)數(shù)小于 3 ,則表示該裝飾器為 類裝飾或者在構(gòu)造函數(shù)上的參數(shù)裝飾器
 // 2. 如果實(shí)參個(gè)數(shù)大于等于3, 則表示為非 1 情況的裝飾器。
 // 2.1 此時(shí)根據(jù)傳入的第四個(gè)參數(shù),來判斷是否存在屬性描述
 // 如果 desc 傳入 null,則獲取當(dāng)前 target key 的屬性描述符給 r 賦值。比如訪問器屬性裝飾器、方法裝飾器
 // 相反如果傳入非 null (通常為 undefined), 則直接返回 desc 。比如屬性裝飾器


 // 此時(shí) r 根據(jù)不同情況,
 // 要么是傳入的 target    (實(shí)參個(gè)數(shù)小于3)
 // 要么是 Object.getOwnPropertyDescriptor(target, key) (實(shí)參個(gè)數(shù)小于3,且 desc 為 null)
 // 要么是 undefined (實(shí)參個(gè)數(shù)小于3, desc 為 undefined)
   r =
     c < 3
       ? target
       : desc === null
       ? (desc = Object.getOwnPropertyDescriptor(target, key))
       : desc,
   d;
 for (var i = decorators.length - 1; i >= 0; i--) {
   // 從數(shù)組的末尾到首部依次遍歷獲得每一個(gè)裝飾方法
   if ((d = decorators[i])) {
     // 同樣判斷參數(shù)個(gè)數(shù)
     // 1. 如果實(shí)參個(gè)數(shù)小于 3, 類裝飾器/構(gòu)造函數(shù)上的參數(shù)裝飾
     // 此時(shí) d 為當(dāng)前裝飾器方法, r 為傳入的 target (Parent)
     // 此時(shí)直接使用當(dāng)前裝飾器進(jìn)行調(diào)用,傳入 d(r) 也就是 d(Parent)
     // 2. 如果實(shí)參個(gè)數(shù)大于 3 ,則調(diào)用當(dāng)前裝飾 d(target, key, r)
     // 3. 如果實(shí)參個(gè)數(shù)等于 3 , 則調(diào)用 d(target, key)
     // 同時(shí)為 r 重新賦值,交給下一次 for 循環(huán)遍歷處理下一個(gè)裝飾器函數(shù)
     r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
   }
 }
 // 最終裝飾器函數(shù)會進(jìn)行返回
 // 如果個(gè)數(shù)大于 3,并且 r 存在 則會返回 Object.defineProperty(target, key, r) ,將返回的 r 當(dāng)作屬性描述符定義在 target key 上
 // 最終返回 r 
 return c > 3 && r && Object.defineProperty(target, key, r), r;
};

函數(shù)最后都會返回r對象,一開始會給予實(shí)參個(gè)數(shù)以及特定參數(shù)進(jìn)行判斷處理,然后基于decorators、target獲得所有裝飾方法,然后拿到裝飾類的原型。

最終,會返回處理后的裝飾器方法 r,在類裝飾器上我們會使用到返回后的 r 重新賦值給當(dāng)前構(gòu)造函數(shù)。

Parent = __decorate([logger, __param(0, paramDecorator)], Parent);

至此,深入淺出裝飾器全過程結(jié)束。

責(zé)任編輯:武曉燕 來源: 量子前端
相關(guān)推薦

2023-08-07 16:07:42

2022-09-26 09:02:54

TS 裝飾器TypeScript

2022-05-10 09:12:16

TypeScript裝飾器

2021-06-17 09:32:17

前端TypeScript 技術(shù)熱點(diǎn)

2025-04-07 04:00:00

AngularTypeScript裝飾器

2023-07-12 08:29:58

TypeScrip元組元素

2023-02-07 07:47:52

Python裝飾器函數(shù)

2010-02-01 17:50:32

Python裝飾器

2021-09-10 06:50:03

TypeScript裝飾器應(yīng)用

2016-11-01 09:24:38

Python裝飾器

2022-09-19 23:04:08

Python裝飾器語言

2023-02-06 08:09:24

TypeScriptES裝飾器

2023-09-04 13:14:00

裝飾器設(shè)計(jì)模式

2023-12-11 15:51:00

Python裝飾器代碼

2024-05-24 11:36:28

Python裝飾器

2017-07-07 17:01:32

裝飾器代碼Python

2021-02-01 14:17:53

裝飾器外層函數(shù)里層函數(shù)

2021-06-01 07:19:58

Python函數(shù)裝飾器

2024-09-12 15:32:35

裝飾器Python

2020-04-13 16:05:25

JS裝飾器前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號