Typescript —— 裝飾器

概念介紹

裝飾器模式(Decorator Pattern)允許像一個現(xiàn)有的對象添加新的功能,同時又不改變其結(jié)構(gòu)义钉,這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式蕊玷,他是作為現(xiàn)有的類的一個包裝。

這種模式創(chuàng)建了一個裝飾類,用來包裝原有的類严沥,并在保持類方法簽名的完整性的前提下,提供了額外的功能中姜。

我們通過下面的實(shí)例來演示裝飾器模式的用法消玄。其中,我們把一個形狀裝飾上不同的顏色丢胚,同時又不改變形狀類

用js實(shí)現(xiàn)一個裝飾器翩瓜,看下原理
const dec = (target, property) => {
    const old = target.prototype[property];
    target.prototype[property] = (msg) => {
        msg = `【${msg}】`;
        old(msg)
    }

}
class Log {
    print(str: string) {
        console.log(str);
    }
}
dec(Log, "print");
const log = new Log()
log.print("你好")

在控制臺里會打印出如下結(jié)果:

【你好】

關(guān)于類、方法携龟、訪問器兔跌、屬性裝飾器(Typescript中)

  • 類裝飾器:接收1個參數(shù)(target)、可以用來監(jiān)視峡蟋、修改和替換類定義
  • 方法裝飾器:接收3個參數(shù)(target坟桅、property、descriptor)可以用來監(jiān)視蕊蝗、修改和替換方法定義
  • 訪問器裝飾器:接收3個參數(shù)(target仅乓、property、descriptor)可以用來監(jiān)視蓬戚、修改和替換訪問器定義
  • 屬性裝飾器:接收2個參數(shù)(target夸楣、property)只能用來監(jiān)視類中是否聲明了某個名字的屬性
  • 參數(shù)裝飾器:接收3個參數(shù)(target、property子漩、paramsIndex)只能用來監(jiān)視一個方法的參數(shù)是否被傳入豫喧。

詳解:
target:對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實(shí)例成員是類的原型對象(prototype)
property:成員的名字
descriptor:成員的屬性描述符
paramsIndex:參數(shù)的下標(biāo)

descriptor簡單介紹

//descriptor 屬性描述符
//在方法裝飾器中
{
  value: [Function],
  writable: true,
  enumerable: true,
  configurable: true
}
//在訪問器裝飾器中
{
  get: [Function: get],
  set: undefined,
  enumerable: false,
  configurable: true
}

類裝飾器

類裝飾器應(yīng)用于類構(gòu)造函數(shù)幢泼,可以用來監(jiān)視嘿棘,修改或替換類定義。

注意?類的構(gòu)造函數(shù)作為其唯一的參數(shù)旭绒,如果類裝飾器返回一個值鸟妙,他會使用提供的構(gòu)造函數(shù)來替換類的聲明

const anotation: Function = (target) => {
    console.log(target.foo);
    console.log(target.method);
    return class extends target {
        newMethod() {
            console.log("Example New Method");
        }
    }
}

@anotation
class Example {
    static foo = "fooValue";
    public method() {
        console.log("Example Public Method");

    }
}
const example = new Example();
example["newMethod"]();

在控制臺里會打印出如下結(jié)果:

fooValue
undefined
Example New Method

方法裝飾器

用來監(jiān)視,修改或者替換方法定義挥吵。

方法裝飾器表達(dá)式會在運(yùn)行時當(dāng)作函數(shù)被調(diào)用重父,傳入下列3個參數(shù)(target,property,descriptor):

  1. target:對于靜態(tài)成員來說是類的構(gòu)造函數(shù)(包含全部的靜態(tài)屬性),對于實(shí)例成員是類的原型對象(包含全部的實(shí)例屬性)忽匈。
  2. property:成員的名字房午。
  3. descriptor:成員的屬性描述符。

第一種return target裝飾函數(shù)(想使用target裝飾函數(shù)丹允,必須return)

// 方法裝飾器(return target)
const anotationMethod: Function = (target, property, descriptor) => {
    const old = target[property];
    target[property] = function () {
        console.log("old calling");
        old();
    }
    return target
}
class Example {
    static foo = "fooValue";

    @anotationMethod
    public method() {
        console.log("Example Public Method");

    }
}
const example = new Example();
example.method();

在控制臺里會打印出如下結(jié)果:

old calling
Example Public Method

2.第二種descriptor.value裝飾函數(shù)(直接修改descriptor.value即可)

// 方法裝飾器(修改descriptor.vaue)
const anotationMethod: Function = (target, property, descriptor) => {
    const old = descriptor.value;
    descriptor.value = function () {
        console.log("old calling");
        old();
    }
}
// 方法裝飾器(修改descriptor.value)
class Example {
    static foo = "fooValue";
    @anotationMethod
    public method() {
        console.log("Example Public Method");

    }
}
const example = new Example();
example.method();

在控制臺會打印出如下結(jié)果

old calling
Example Public Method

訪問器裝飾器

訪問器裝飾器應(yīng)用于訪問器的 屬性描述符并且可以用來監(jiān)視郭厌,修改或替換一個訪問器的定義袋倔。

注意? TypeScript不允許同時裝飾一個成員的get和set訪問器。取而代之的是折柠,一個成員的所有裝飾的必須應(yīng)用在文檔順序的第一個訪問器上宾娜。這是因?yàn)椋谘b飾器應(yīng)用于一個屬性描述符時扇售,它聯(lián)合了get和set訪問器前塔,而不是分開聲明的。

訪問器裝飾器表達(dá)式會在運(yùn)行時當(dāng)作函數(shù)被調(diào)用承冰,傳入下列3個參數(shù):

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù)华弓,對于實(shí)例成員是類的原型對象。
  2. 成員的名字困乒。
  3. 成員的屬性描述符寂屏。

注意? 如果代碼輸出目標(biāo)版本小于ES5,Property Descriptor將會是undefined娜搂。


const configurable: Function = (target, property, descriptor: PropertyDescriptor) => {
    console.log(target);
    console.log(property);
    console.log(descriptor);
    descriptor.get = () => {
        return "楊志強(qiáng)"
    }
}
class Point {
    private _name: string = "張三";

    @configurable
    get name() {
        return this._name
    }
    set name(newVal) {
        this._name = newVal;
    }
}

const point = new Point();
console.log(point.name);

在控制臺會輸出如下結(jié)果

Point {}
name
{
  get: [Function: get],
  set: [Function: set],
  enumerable: false,
  configurable: true
}
楊志強(qiáng)

屬性裝飾器

屬性裝飾器只能用來監(jiān)視類中是否聲明了某個名字的屬性凑保。
因?yàn)槟壳皼]有辦法在定義一個原型對象的成員時描述一個實(shí)例屬性,并且沒辦法監(jiān)視或修改一個屬性的初始化方法涌攻。返回值也會被忽略。

屬性裝飾器表達(dá)式會在運(yùn)行時當(dāng)作函數(shù)被調(diào)用频伤,傳入下列倆個參數(shù)

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù)恳谎,對于實(shí)例成員是類的原型對象。
  2. 成員的名字憋肖。

注意 屬性描述符不會作為參數(shù)傳入屬性裝飾器因痛,這與Typescript是如何初始化屬性裝飾器有關(guān)

import "reflect-metadata"
const formatMetadataKey = Symbol("format");
const format = (value: string) => {
    return Reflect.metadata(formatMetadataKey, value);
}
function getFormat(target, property) {
    return Reflect.getMetadata(formatMetadataKey, target, property);
}

class Point {
    @format("Hello World")
    public name: string = "張三";

    greet(property) {
        return getFormat(this, property);
    }
}

const point = new Point();
console.log(point.greet("name"));

這個@format("Hello World")裝飾器是個 裝飾器工廠。 當(dāng) @format("Hello World")被調(diào)用時岸更,它會添加一條這個屬性的元數(shù)據(jù)鸵膏,通過reflect-metadata庫里的Reflect.metadata函數(shù)。 當(dāng) getFormat被調(diào)用時怎炊,它讀取格式的元數(shù)據(jù)谭企。

在控制臺會打印如下結(jié)果

Hello World

參數(shù)裝飾器

參數(shù)裝飾器只能用來監(jiān)視一個方法的參數(shù)是否被傳入。

參數(shù)裝飾器表達(dá)式會在運(yùn)行時當(dāng)作函數(shù)被調(diào)用评肆,傳入下列3個參數(shù):

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù)债查,對于實(shí)例成員是類的原型對象。
  2. 成員的名字瓜挽。
  3. 參數(shù)在函數(shù)參數(shù)列表中的索引盹廷。

import "reflect-metadata";
const requiredKey = Symbol("requeiredKey")
const required: Function = (target, property, paramIndex) => {
    let paramIndexs = Reflect.getMetadata(requiredKey, target, property) || [];
    paramIndexs.push(paramIndex);
    Reflect.defineMetadata(requiredKey, paramIndexs, target, property);
}

// 此處為什么能用getOwnMetadata也能接收到?
// 答:因?yàn)檠b飾器接收的target對于實(shí)例成員來說類的原型對象(prototype),對于靜態(tài)成員來說是類的構(gòu)造函數(shù)久橙,所以我們r(jià)equired和validate指向同一個對象(target.prototype),所以能獲取元數(shù)據(jù)
const validate: Function = (target, property, descriptor: PropertyDescriptor) => {
    const method = descriptor.value;
    descriptor.value = function () {
        const requeiredParamers = Reflect.getOwnMetadata(requiredKey, target, property);
        if (requeiredParamers) {
            for (let paramIndex of requeiredParamers) {
                if (paramIndex >= arguments.length || arguments[paramIndex] === undefined) {
                    throw new Error("參數(shù)不正確");
                }
            }
        }
        method.apply(this, arguments);
    }

}

class User {
    name: string = "張三"
    @validate
    info(@required id?: number) {
        console.log(`用戶id為${id}`);

    }

}
const user = new User();
user.info(1);

在控制臺里面打印會打印如下結(jié)果

用戶id為1

關(guān)于元數(shù)據(jù)方法的補(bǔ)充

Reflect.getMetadata(metadataKey,target,property):獲取目標(biāo)對象或其原型鏈上提供的元數(shù)據(jù)鍵的元數(shù)據(jù)值俄占。
Reflect.getOwnMetadata(metadataKey,target,property)::獲取目標(biāo)對象上提供的元數(shù)據(jù)鍵的元數(shù)據(jù)值(目標(biāo)對象指的不是this管怠,而是未實(shí)例化的這個類)。
Reflect.defineMetadata(metadataKey,value,target,property):在目標(biāo)上定義唯一的元數(shù)據(jù)項(xiàng)缸榄。
Reflect.metadata(metadataKey,value):可用于類渤弛、類成員或參數(shù)的默認(rèn)元數(shù)據(jù)裝飾器工廠。(一般直接return)

關(guān)于裝飾器的執(zhí)行順序

如果我們使用裝飾器工廠的話碰凶,可以通過下面的例子來觀察它們求值的順序:

function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}

在控制臺里面打印會打印如下結(jié)果

f(): evaluated
g(): evaluated
g(): called
f(): called
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暮芭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子欲低,更是在濱河造成了極大的恐慌辕宏,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砾莱,死亡現(xiàn)場離奇詭異瑞筐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)腊瑟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門聚假,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人闰非,你說我怎么就攤上這事膘格。” “怎么了财松?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵瘪贱,是天一觀的道長。 經(jīng)常有香客問我辆毡,道長菜秦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任舶掖,我火速辦了婚禮球昨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘眨攘。我一直安慰自己主慰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布鲫售。 她就那樣靜靜地躺著河哑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪龟虎。 梳的紋絲不亂的頭發(fā)上璃谨,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼佳吞。 笑死拱雏,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的底扳。 我是一名探鬼主播铸抑,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼衷模!你這毒婦竟也來了鹊汛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阱冶,失蹤者是張志新(化名)和其女友劉穎刁憋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體木蹬,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡至耻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了镊叁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尘颓。...
    茶點(diǎn)故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晦譬,靈堂內(nèi)的尸體忽然破棺而出疤苹,到底是詐尸還是另有隱情,我是刑警寧澤敛腌,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布卧土,位于F島的核電站,受9級特大地震影響迎瞧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逸吵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一凶硅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扫皱,春花似錦足绅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至段多,卻和暖如春首量,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工加缘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸭叙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓拣宏,卻偏偏與公主長得像沈贝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子勋乾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 訪問器裝飾器 訪問器裝飾器聲明在一個訪問器的聲明之前(緊靠著訪問器聲明)宋下。 訪問器裝飾器應(yīng)用于訪問器的 屬性描述符...
    2o壹9閱讀 1,046評論 1 49
  • 裝飾器求值 類中不同聲明上的裝飾器將按以下規(guī)定的順序應(yīng)用: 參數(shù)裝飾器,然后依次是方法裝飾器辑莫,訪問符裝飾器学歧,或?qū)傩?..
    2o壹9閱讀 848評論 1 49
  • 什么是裝飾器 裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明摆昧,方法撩满, 訪問符,屬性或參數(shù)上绅你。 裝飾器使用 @e...
    涅槃快樂是金閱讀 30,389評論 3 17
  • TypeScript 與 ES6 的裝飾器伺帘? ES6 的裝飾器是一種函數(shù),寫成@ + 函數(shù)名忌锯。它可以放在類和類方法...
    CondorHero閱讀 177評論 0 1
  • 之前整理過《Java注解(批注)的基本原理[https://www.zhoulujun.cn/html/java/...
    zhoulujun閱讀 611評論 0 1