分享一下學習裝飾器(
Decorators
)的所得express
裝飾器是TypeScript
提供的最強大的功能之一,它使咱們可以以乾淨的聲明性方式擴展類和方法的功能。裝飾器目前是JavaScript
的第2階段提議,但在TypeScript
生態系統中已受到歡迎,主要的開放源代碼項目(例如Angular
)正在使用裝飾器。json
本人工做中是使用Angular8進行項目一個項目的開發的,接觸裝飾器這一方面也就比較多,因此就趁着週末整理了一篇關於裝飾器Decorators
的文章,也但願能幫助到學習這方面的同窗們。廢話很少說,下面我們進入正題。c#
Decorators
首先咱們要在tsconfig.json
裏面啓用experimentalDecorators編譯器選項:api
命令行:tsc --target ES5 --experimentalDecorators
數組
tsconfig.json:bash
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
複製代碼
咱們先明確兩個概念:app
@expression
的形式實際上是一個語法糖,expression
求值後必須也是一個函數,它會在運行時被調用,被裝飾的聲明信息作爲參數傳入。JavaScript
中的Class
其實也是一個語法糖。例如咱們在Javascript
中聲明一個Class
:ide
Class Animal {
eat() {
console.log('eat food')
}
}
複製代碼
上面這個Animal類實際等同於下面這樣:函數
function Animal() {}
Object.defineProperty(Animal.prototype, 'eat', {
value: function() { console.log('eat food'); },
enumerable: false,
configurable: true,
writable: true
});
複製代碼
類裝飾器應用於類的構造函數,可用於觀察、修改或替換類定義。學習
function setDefaultDesc(constructor: Function){
constructor.prototype.desc = '類裝飾器屬性'
}
@setDefaultDesc
class Animal {
name: string;
desc: string;
constructor() {
this.name = 'dog';
}
}
let animal= new Animal();
console.log(animal.desc) // '類裝飾器屬性'
複製代碼
下面是使用重載函數的例子。
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
return class extends constructor {
newProperty = "new property";
desc = "override";
}
}
@classDecorator
class Animal {
property = "property";
desc: string;
constructor(m: string) {
this.desc = m;
}
}
console.log(new Animal("world")); // Animal: {property: "property", desc: "override", newProperty: "new property" }
複製代碼
這部分代碼的含義是:被classDecorator
裝飾的類裏面若是不存在newProperty
或desc
屬性,會增長相應的屬性和對應的value
,若是存在該屬性就會重寫該屬性的value
。
方法裝飾器聲明在一個方法的聲明以前(緊靠着方法聲明)。 它會被應用到方法的 屬性描述符上,能夠用來監視,修改或者替換方法定義。
方法裝飾器表達式會在運行時看成函數被調用,傳入下列3個參數:
Employee.prototype
)Object.getOwnPropertyDescriptor(Employee.prototype, propertyKey)
function logMethod(
target: Object,
propertyName: string,
propertyDesciptor: PropertyDescriptor): PropertyDescriptor {
// target === Employee.prototype
// propertyName === "greet"
// propertyDesciptor === Object.getOwnPropertyDescriptor(Employee.prototype, "greet")
const method = propertyDesciptor.value;
propertyDesciptor.value = function (...args: any[]) {
// 將參數列表轉換爲字符串
const params = args.map(a => JSON.stringify(a)).join();
// 調用該方法並讓它返回結果
const result = method.apply(this, args);
// 轉換結果爲字符串
const r = JSON.stringify(result);
// 在控制檯中顯示函數調用細節
console.log(`Call: ${propertyName}(${params}) => ${r}`);
// 返回調用的結果
return result;
}
return propertyDesciptor;
};
class Employee {
constructor(
private firstName: string,
private lastName: string
) {
}
@logMethod
greet(message: string): string {
return `${this.firstName} ${this.lastName} : ${message}`;
}
}
const emp = new Employee('三月風情', '陌上花開');
emp.greet('三月風情陌上花'); // return: '三月風情 陌上花開 : 三月風情陌上花'
複製代碼
訪問器只是類聲明中屬性的getter和setter部分。 訪問器裝飾器是在訪問器聲明以前聲明的。訪問器裝飾器應用於訪問器的屬性描述符,可用於觀察、修改或替換訪問器的定義。
訪問器裝飾器表達式會在運行時看成函數被調用,傳入下列3個參數:
Employee.prototype
)Object.getOwnPropertyDescriptor(Employee.prototype, propertyKey)
下面是使用了訪問器裝飾器(@configurable
)的例子,應用於Point
類的成員上:
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() { return this._x; }
@configurable(false)
get y() { return this._y; }
}
複製代碼
屬性裝飾器函數須要兩個參數:
function logParameter(target: Object, propertyName: string) {
// 屬性的值
let _val = target[propertyName];
// 屬性的get方法
const getter = () => {
console.log(`Get: ${propertyName} => ${_val}`);
return _val;
};
// 屬性的set方法
const setter = newVal => {
console.log(`Set: ${propertyName} => ${newVal}`);
_val = newVal;
};
// 刪除屬性.
if (delete target[propertyName]) {
// 使用getter和setter建立新屬性
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
class Employee {
@logParameter
name: string;
}
const emp = new Employee();
emp.name = '陌上花開'; // Set: name => 陌上花開
console.log(emp.name);
// Get: name => 陌上花開
// 陌上花開
複製代碼
參數裝飾函數須要三個參數:
function logParameter(target: Object, propertyName: string, index: number) {
// 爲相應的方法生成元數據
// 保持被修飾參數的位置
const metadataKey = `log_${propertyName}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}
class Employee {
greet(@logParameter message: string): string {
return `hello ${message}`;
}
}
const emp = new Employee();
emp.greet('hello');
複製代碼
在上面的代碼示例中:target
爲Employee的實例emp
,propertyName
的值爲greet
,index
的值爲0
。
咱們先假設這樣一個場景,好比咱們須要幾個裝飾器,分別把一個類中的部分屬性、類自己、方法、參數的名稱打印出來,這時候咱們該怎麼作。
import { logClass } from './class-decorator';
import { logMethod } from './method-decorator';
import { logProperty } from './property-decorator';
import { logParameter } from './parameter-decorator';
// 假設咱們已經有了上面這些裝飾器,下面咱們就該這樣作。
function log(...args) {
switch (args.length) {
case 3:
// 能夠是方法裝飾器仍是參數裝飾器
if (typeof args[2] === "number") {
// 若是第三個參數是number那麼它的索引就是它的參數裝飾器
return logParameter.apply(this, args);
}
return logMethod.apply(this, args);
case 2:
// 屬性裝飾器
return logProperty.apply(this, args);
case 1:
// 類裝飾器
return logClass.apply(this, args);
default:
// length長度在1,2,3外的狀況
throw new Error('Not a valid decorator');
}
}
@log
class Employee {
@log
private name: string;
constructor(name: string) {
this.name = name;
}
@log
greet(@log message: string): string {
return `${this.name} says: ${message}`;
}
}
複製代碼
裝飾器工廠就是一個簡單的函數,它返回一種類型的裝飾器。
@Reflect.metadata('name', 'A')
class A {
@Reflect.metadata('hello', 'world')
public hello(): string {
return 'hello world'
}
}
Reflect.getMetadata('name', A) // 'A'
Reflect.getMetadata('hello', new A()) // 'world'
複製代碼
反射, ES6+ 加入的 Relfect 就是用於反射操做的,它容許運行中的 程序對自身進行檢查,或者說「自審」,並能直接操做程序的內部屬性和方法,反射這個概念其實在 Java/c# 等衆多語言中已經普遍運用了
再來一個小例子來看下:
function logParameter(target: Object, propertyName: string, index: number) {
// 從目標對象獲取元數據
const indices = Reflect.getMetadata(`log_${propertyName}_parameters`, target, propertyName) || [];
indices.push(index);
// 將元數據定義爲目標對象
Reflect.defineMetadata(`log_${propertyName}_parameters`, indices, target, propertyName);
}
// 屬性裝飾器使用反射api來獲取屬性的運行時類型
export function logProperty(target: Object, propertyName: string): void {
// 從對象中獲取屬性的設計類型
var t = Reflect.getMetadata("design:type", target, propertyName);
console.log(`${propertyName} type: ${t.name}`); // name type: String
}
class Employee {
@logProperty
private name: string;
constructor(name: string) {
this.name = name;
}
greet(@logParameter message: string): string {
return `${this.name} says: ${message}`;
}
}
複製代碼
在上面的例子中,咱們使用了反射元數據設計鍵[design:type]。目前只有三種:
design:type
design:paramtypes
design:returntype
這篇文章主要介紹了類裝飾器,方法裝飾器,訪問器裝飾器,屬性裝飾器,參數裝飾器,裝飾器工廠,和元數據Reflection。也是我學習過程當中的一些總結。每週都會持續更新不一樣的技術,喜歡的同窗能夠點贊加關注,你們一塊兒進步。若是有想學習某方面技術的同窗也歡迎評論區留言,我會努力寫出你們感興趣的內容。