閑言
一切都要從公司里的一名老哥給我看的一段代碼提及。。。
@controller('/user')
@auth
@post('/login')
async userLogin = (name, pass) => {
@required
// ...
}
以下為對話:
我:這不是潤飾器嗎(由於之前看到過@這個東西)
老哥:還不錯嘛,曉得是潤飾器,那你曉得這一段想表達什麼意思嗎
我:這是路由?(一臉懵逼,然則看到了/user和post另有/login,內心想豈非這是路由)
老哥:穩!
我:震動了,還能夠如許寫路由。不可,歸去我要好悅目看這個破@
由此最先了潤飾器的進修~~~
嚶嚶嚶~~
初識Decorator
起首申明,潤飾器在JavaScript中還處於發起階段,現在還不能夠被大部份環境支撐,而且以後另有可能會轉變。假如想要運用該特徵請用Babel舉行轉碼或許運用JavaScript的超集TypeScript
在ES6中增加了類的相干定義和操縱(比方class和extends),如許方便了我們單個類的操縱,然則當我們想要在多個不同類之間同享、復用一些要領的時刻,會發明變得不那麼文雅,所以decorator被提了出來。
小小的demo:以@作為標識符,既能夠作用於類,也能夠作用於類屬性
@decorator
class Cat {}
class Dog {
@decorator
run() {}
}
潤飾器的運用
既然decorator與類相干,我們先相識一下類(這裏只是簡樸引見,想要細緻相識的,請自行查閱相干材料,引薦阮一峰的ES6入門)
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 {}