一切都要從公司裏的一位老哥給我看的一段代碼提及。。。javascript
@controller('/user') @auth @post('/login') async userLogin = (name, pass) => { @required // ... }
如下爲對話:html
我:這不是修飾器嗎(由於以前看到過@這個東西)老哥:還不錯嘛,知道是修飾器,那你知道這一段想表達什麼意思嗎java
我:這是路由?(一臉懵逼,可是看到了/user和post還有/login,內心想難道這是路由)api
老哥:穩!async
我:震驚了,還可以這樣寫路由。不行,回去我要好好看看這個破@函數
由此開始了修飾器的學習~~~
嚶嚶嚶~~post
首先說明,修飾器在JavaScript中還處於提議階段,目前還不可以被大部分環境支持,而且以後還有可能會改變。若是想要使用該特性請用Babel進行轉碼或者使用JavaScript的超集TypeScript
在ES6中增長了類的相關定義和操做(好比class和extends),這樣方便了咱們單個類的操做,可是當咱們想要在多個不一樣類之間共享、複用一些方法的時候,會發現變得不那麼優雅,因此decorator被提了出來。學習
小小的demo:以@做爲標識符,既能夠做用於類,也能夠做用於類屬性ui
@decorator class Cat {} class Dog { @decorator run() {} }
既然decorator與類相關,咱們先了解一下類(這裏只是簡單介紹,想要詳細瞭解的,請自行查閱相關資料,推薦阮一峯的ES6入門)this
class Cat { constructor(name) { this.name = name } say () { console.log("miao ~") } }
這實際上是一個語法糖,具體的實現是經過Object.defineProperty()
方法來操做的,該方法語法以下:
Object.defineProperty(obj, prop, descriptor) -> obj: 要在其上定義屬性的對象 -> prop: 要定義或修改的屬性的名稱 -> descriptor:要被定義或修改的屬性描述符 返回:傳遞給該方法的對象(即obj)
因此上面那個Cat的代碼實際上在執行時是這樣的:
function Cat() {} Object.defineProperty(Cat.prototype, "say", { value: function() {console.log("miao ~")}, // 該屬性對應的值 enumerable: false, // 爲true時,纔可以出如今對象的枚舉屬性中 configurable: true, // 爲true時,該屬性描述符纔可以被改變 writable: true // 爲true時,value才能被賦值運算符改變 }) // 返回Cat.prototype
function isAnimal(target) { target.isAnimal = true return target // 返回的是傳遞給該函數的對象 } @isAnimal class Cat { // ... } console.log(Cat.isAnimal) // true
上面的代碼基本等同於:
Cat = isAnimal(function Cat() {})
function readonly(target, name, descriptor) { descriptor.writable = false return descriptor } class Cat { @readonly say () { console.log("miao ~") } } let kitty = new Cat() kitty.say = function () { console.log("wow ~") } kitty.say() // miao ~
經過將descriptor屬性描述符的writable設置爲false,使得say方法只讀,後面對它進行的賦值操做不會生效,調用的依舊是以前的方法。
有木有以爲readonly()方法的參數似曾相識?它和上文介紹ES6中的類中提到的Object.defineProperty()
是同樣的。
其實修飾器在做用於屬性的時候,其實是經過Object.defineProperty
進行擴展和封裝的。因此上面的代碼其實是這樣的:
let descriptor = { value: function() { console.log("miao ~") }, enumerable: false, configurable: true, writable: true } descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor Object.defineProperty(Cat.prototype, "say", descriptor)
當修飾器做用於類時,咱們操做的對象是類自己,當修飾器做用於類屬性時,咱們操做的對象既不是類自己也不是類屬性,而是它的描述符(descriptor)。
固然了,你也能夠直接在target上進行擴展和封裝。
function fast(target, name, descriptor) { target.speed = 20 let run = descriptor.value() descriptor.value = function() { run() console.log(`speed ${this.speed}`) } return descriptor } class Rabbit { @fast run() { console.log("running~") } } let bunny = new Rabbit() bunny.run() // running ~ // speed 20 console.log(bunny.speed) // 20
讓咱們回到文章開始講到的代碼,它怎麼閱讀呢
@controller("/api/user") export class userController { @post("/add") @required({ body: ["telephone", 'key1'] }) async addUser(ctx, next) {} @get("/userlist") async userList(ctx, next) {} }
讓咱們先看@controller("/api/user")
const symbolPrefix = Symbol('prefix') export const controller = path => target => (target.prototype[symbolPrefix] = path)
做用是將/api/user
做爲prefixPath,此時target爲userController {}
,在該target的原型上設置path
再接着看@post("/add")
// 如下代碼省略了部分細節 ... // export const post = path => (target, name, descriptor) => {} routerMap.set({ target: target, ...conf }, target[name]) // name爲addUser for(let [conf, func] of routerMap) { // conf爲{target, path} 該target爲userController {},path爲@post()傳遞進來的參數 let prefixPath = conf.target[symbolPrefix] // 爲 /api/user let routePath = prefix + path // 爲 /api/user/add } // 獲得了api路徑(routePath),也獲得了該api路徑所執行的方法(func) // get同理 ...
原理基本上都是相似的,處理修飾器傳遞的參數,獲得本身想要的結果。
額,經歷過上面的知識瞭解,應該能大概夠理解這段代碼了吧~
修飾器容許你在類和方法定義的時候去註釋或者修改它。修飾器是一個做用於函數的表達式,它接收三個參數 target、 name 和 descriptor , 而後可選性的返回被裝飾以後的 descriptor 對象。
你也能夠疊加使用,就像這樣
@post("/add") @required({ body: ["telephone", 'key1'] }) async xxx {}