TS下的裝飾者模式(Decorator)

1.一、裝飾者模式

裝飾者模式就是動態的給類或對象增長功能的設計模式。在程序運行時動態的給一個具有基礎功能的類或對象添加新的功能,而且不會改變會破壞基礎類和對象的功能。先提煉出產品的最小可用產品,再經過快速迭代的方式添加功能。javascript

1.二、Typescript下的實現

Javascript裏的裝飾器目前處在建議徵集的第二階段,不被瀏覽器所支持,若是想要提早使用這個新特性就須要Babel,Typescript等工具進行轉譯。這裏介紹Typescript下的用法。html

首先在全局安裝typescriptjava

npm install typescript -g

而後新建一個後綴爲.ts的typescript文件,這裏咱們新建一個demo.tsnode

  • 新建一個Greeter類
class Greeter {
    constructor() {}
    greet(subject:any) {
        console.log(`hello ${subject}!`);
    }
}
const greet = new Greeter();
greet.greet(`decorator`);

將ts文件編譯爲js運行chrome

tsc demo.ts --target ES5 --experimentalDecorators
# 若是本地沒安裝node能夠把demo.js中的代碼複製到chrome控制檯測試
node demo.js

運行結果typescript

hello decorator!

下面給它加上一個裝飾器,使greet方法可以在成功執行後作一個日誌記錄npm

class Greeter {
    constructor() { }
    @logSuccess
    greet(subject: string) {
        console.log(`hello ${subject}!`);
    }
}
function logSuccess(target: any, key: any, descriptor: any) {
    const func = descriptor.value;
    descriptor.value = (...args: any[]) => {
        func.apply(target, args)
        console.log(`greet successfully!`)
    }
}
const greet = new Greeter();
greet.greet(`decorator`);

編譯後運行結果
運行結果設計模式

hello decorator!
greet successfully!

正如咱們所見Greeter的原方法greet()在執行完以後執行了console.log(`greet successfully!`)瀏覽器

logSuccess(target,key,descriptor)爲何須要傳入這三個參數?Decorators的實現使用了ES5的 Object.defineProperty 方法,這三個參數也和這個方法的參數一致。裝飾器的本質就是一個函數語法糖,經過Object.defineProperty來修改類中一些屬性,descriptor參數也是一個對象,是針對key屬性的描述符,裏面有控制目標對象的該屬性是否可寫的writable屬性等。bash

接下來咱們將該日至系統簡單完善一下

除了打印該方法執行成功,再添加對其運行時錯誤的日誌輸出

class Greeter {
    constructor() { }
    @log
    greet(subject: string) {
        console.log(`hello ${subjects}!`);
    }
}
function log(target: any, key: any, descriptor: any) {
    const func = descriptor.value;
    descriptor.value = (...args: any[]) => {
        try {
            func.apply(target, args)
            console.log(`greet successfully!`)
        } catch (err) {
            console.log(`greet error : ${err}`)
        }
    }
}
const greet = new Greeter();
greet.greet(`decorator`);

這裏咱們特地使用了一個未聲明的變量subjects來觸發一個錯誤
查看運行結果

greet error : ReferenceError: subjects is not defined

這樣就實現了一個簡單的日誌系統

1.3 裝飾者模式與工廠函數

若是想對不一樣的對象應用同一個decorator,但同時又須要經過傳參來控制一些差異(裝飾器器函數須要保留(target,key,descriptor)三個參數),這時就須要工廠函數來幫咱們生成一個裝飾器函數。

經過裝飾器來使一個屬性變得只讀👇

class Greeter {
    constructor() { }
    @readonly(true)
    demo() {
        console.log(`i am readonly`)
    }
}
function readonly(readonly: boolean) {
    return function (target: any, key: any, descriptor: any) {
        descriptor.writable = !readonly
    }
}
const greet = new Greeter();
greet.demo = function () {
    console.log(`new greet`)
}
greet.demo()

輸出結果

i am readonly

多個裝飾器能夠疊加做用

class Greeter {
    constructor() { }
    @readonly(false)
    @readonly(true)
    demo() {
        console.log(`i am readonly`)
    }
}
function readonly(readonly: boolean) {
    return function (target: any, key: any, descriptor: any) {
        descriptor.writable = !readonly
    }
}
const greet = new Greeter();
greet.demo = function () {
    console.log(`new greet`)
}
greet.demo()

輸出結果

new greet

當有多個裝飾器同時做用在一個對象的屬性上時👇:

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

疊加效果相似這樣g(f(method))

1.4 裝飾器做用在類上

做用在類上的裝飾器中的參數target再也不是類的prototype,而是類自己,所以他也沒有keydescriptor兩個屬性。

下面的裝飾器給Greeter類動態增長了greet()函數

@decrator()
class Greeter {
    constructor() { }
    greet?: any
}
function decrator() {
    return function (target: any) {
        console.log(target.prototype)
        if (target.prototype && !target.prototype.greet) {
            target.prototype.greet = function () {
                console.log(`hello decrator!`)
            }
        }
    }
}
const greet = new Greeter();
if (greet.greet) {
    greet.greet()
}

輸出

Greeter {}
hello decrator!

這裏有同窗可能對原型鏈的知識不太瞭解或者生疏了,簡單提醒一下👇

//構造函數
function Cons(){}
//對象實例
const cons = new Cons();

Cons.prototype === cons.__proto__;  // true
Cons.prototype.constructor === Cons;  // true

原型鏈的教程能夠參考一下prototype對象

相關文章
相關標籤/搜索