成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

探索TypeScript:裝飾器

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

前言

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

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

裝飾器一般用于處理一些與類以及類屬性本身無關的邏輯,例如: 一個類方法的執行耗時統計或者記錄日志,可以單獨拿出來寫成裝飾器。

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

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

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

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

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

類裝飾器

類裝飾器僅接受一個參數,該參數表示類本身。

同時,如果類裝飾器返回一個值,它會使用提供的構造函數來替換類的聲明。

比如:

// 類裝飾器,接受一個參數即為類本身
// 將裝飾后的類以及類的原型全部凍結變為不可擴展以及不可修改
function freeze(constructor: Function) {
  Object.freeze(constructor); // 凍結裝飾的類
  Object.freeze(constructor.prototype); // 凍結類的原型
}


// 調用 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

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

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

  }
}

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

}

const instance = new Parent()

console.log(instance) // Child {}

方法裝飾器

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

方法裝飾器接受三個參數:

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

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

比如下面的例子,我們使用方法裝飾器修改類的實例方法,將 greet 方法變為不可枚舉:

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

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

    // 將該方法(Greeter.prototype.greet) 變為不可枚舉
    descriptor.enumerable = value;
  };
}


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

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

屬性訪問器裝飾器

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

當屬性裝飾器被調用時,和方法裝飾器同樣會接受三個參數,分別為:

  • 如果當前屬性訪問器為類的靜態屬性訪問器,那么屬性訪問器修飾器接受的第一個參數則為當前類的構造函數。否則,如果修飾的為實例上的屬性訪問器,則第一個參數為類的原型。
  • 第二個參數為當前被修飾的成員名稱。
  • 第三個參數為當前被修飾的屬性描述符。

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

比如,當我們使用裝飾器來修飾當前類上的屬性訪問器時:

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

class Person {

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

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

參數裝飾器

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

  • 當參數修飾器修飾的所在方法為類的構造函數/靜態方法時,第一個參數表示類的構造函數(類本身)。反之,當參數修飾器修飾的參數所在的方法為實例方法時,此時第一個參數代表類的原型。
  • 如果修飾的為類的靜態/實例方法時,第二個參數為當前參數修飾器所在方法的方法名。如果參數修飾器所在的方法為類的構造函數參數修飾時,此時第二個參數為 undefined。
  • 第三個參數,表示當前參數所在方法的位置索引。

我們依次來看看參數裝飾器分別裝飾類的構造函數、類的靜態方法上的參數以及類的實例方法上的參數不同表現:

參數修飾器所在方法為修飾類的構造函數:

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
}

至此所有常見的類裝飾器都介紹完了,其實本質的裝飾器函數入參都是一致的,第一個參數是裝飾器所在的類名、第二個參數是裝飾參數,接下來我們看一下裝飾器的實現原理。

實現原理

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

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

會發現所有裝飾器都在調用__decorate方法,并且不同的裝飾器,對于__decorate方法的入參也是通用型很強。

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

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

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

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


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

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

最終,會返回處理后的裝飾器方法 r,在類裝飾器上我們會使用到返回后的 r 重新賦值給當前構造函數。

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

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

責任編輯:武曉燕 來源: 量子前端
相關推薦

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 技術熱點

2025-04-07 04:00:00

AngularTypeScript裝飾器

2023-07-12 08:29:58

TypeScrip元組元素

2023-02-07 07:47:52

Python裝飾器函數

2010-02-01 17:50:32

Python裝飾器

2021-09-10 06:50:03

TypeScript裝飾器應用

2016-11-01 09:24:38

Python裝飾器

2022-09-19 23:04:08

Python裝飾器語言

2023-02-06 08:09:24

TypeScriptES裝飾器

2017-07-07 17:01:32

裝飾器代碼Python

2021-02-01 14:17:53

裝飾器外層函數里層函數

2021-06-01 07:19:58

Python函數裝飾器

2023-12-11 15:51:00

Python裝飾器代碼

2024-05-24 11:36:28

Python裝飾器

2023-09-04 13:14:00

裝飾器設計模式

2024-09-12 15:32:35

裝飾器Python

2020-04-13 16:05:25

JS裝飾器前端
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91性高湖久久久久久久久_久久99 | 色狠狠桃花综合 | 国产日韩欧美电影 | 91香蕉| 本地毛片| 欧美激情精品久久久久久变态 | 欧美成人一区二区 | 亚洲一区中文字幕在线观看 | 亚洲国产69 | 国产香蕉视频在线播放 | 粉嫩一区二区三区四区公司1 | 丝袜 亚洲 另类 欧美 综合 | 亚洲人免费视频 | 久久久久亚洲国产| 亚洲网站观看 | 俺去俺来也www色官网cms | 黄色在线播放视频 | 亚洲欧美国产精品一区二区 | 国产一区不卡 | 人人看人人干 | 久久里面有精品 | 国产高清在线精品 | 成人黄色电影在线播放 | 浮生影院免费观看中文版 | 免费的一级视频 | 国产精品一区在线观看你懂的 | 精品国产免费一区二区三区五区 | 欧美日韩国产欧美 | 成人av电影在线观看 | 亚洲精品二区 | av免费观看网站 | 国产精品日韩一区 | 99日韩 | 伊人二区 | 日本欧美在线视频 | 成人精品一区 | 久久久资源 | 国产日韩精品久久 | 成人久久18免费网站 | 久久com| 一区二区三区国产精品 |