裝飾者模式就是動態的給類或對象增長功能的設計模式。在程序運行時動態的給一個具有基礎功能的類或對象添加新的功能,而且不會改變會破壞基礎類和對象的功能。先提煉出產品的最小可用產品,再經過快速迭代的方式添加功能。javascript
Javascript裏的裝飾器目前處在建議徵集的第二階段,不被瀏覽器所支持,若是想要提早使用這個新特性就須要Babel,Typescript等工具進行轉譯。這裏介紹Typescript下的用法。html
首先在全局安裝typescriptjava
npm install typescript -g
而後新建一個後綴爲.ts
的typescript文件,這裏咱們新建一個demo.ts
node
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
這樣就實現了一個簡單的日誌系統
若是想對不一樣的對象應用同一個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))
做用在類上的裝飾器中的參數target再也不是類的prototype
,而是類自己,所以他也沒有key
與descriptor
兩個屬性。
下面的裝飾器給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對象