先看一段代碼
import {Controller, Path, GET, POST, PathParam, BodyParam} from 'iwinter'
@Path('/api/orders')
class OrdersController extends Controller {
@GET
@Path('/:name/:id', (ctx, next)=> ~~ctx.params.id > 20)
getAllOrders(@PathParam('id') id: number, @PathParam('name') name: string){
return [{
id: id, name, content: 'test', author: 'test', comments: []
}];
}
@POST
@Path('/add')
addPost(@BodyParam('order') order: object){
return order
}
}
export default OrdersController
這是一段TypeScript上 koa 路由類的寫法,注意到在其中,使用了@Paht @Get
的寫法, 并且在入?yún)⒅幸灿?code>@PathParam('id') id: number這樣的寫法靠娱。這就是裝飾器柬脸。其中 @Path('/api')
中的API是這個裝飾器的入?yún)⑺嘉粒谶@里是注解篙骡,因為這個框架通過Reflect.defineMetadata
將這個入?yún)懭氲搅嗽摲椒ㄖ小T诟闱暹@些復雜的概念之前咕幻,我們先弄明白兩個最基礎的概念
裝飾器和注解
- 裝飾器(Decorator) 僅提供定義劫持渔伯,能夠?qū)︻惣捌浞椒ā⒎椒ㄈ雲(yún)⒁蕹獭傩缘亩x并沒有提供任何附加元數(shù)據(jù)的功能锣吼。
- 注解(Annotation) 僅提供附加元數(shù)據(jù)支持,并不能實現(xiàn)任何操作蓝厌。需要另外的 Scanner 根據(jù)元數(shù)據(jù)執(zhí)行相應操作玄叠。
注意到裝飾器是對類及其方法、入?yún)⑼靥帷傩孕袨榈男薷亩潦眩⒔庵皇翘砑釉獢?shù)據(jù),不能修改行為。
在實際的開發(fā)過程中寺惫,我們通過注解添加元數(shù)據(jù)疹吃,裝飾器再獲取這些元數(shù)據(jù)完成對類或者方法的修改,下面開始對修改一個類
實際操作
class A {
}
首先我們聲明了一個什么也沒有類西雀,接著我們聲明第一個修改器方法,并且作用在類上
@modifyClass
class A {
}
function modifyClass(target: any) {
target.prototype.extraProp = 'decorator'
}
在裝飾器的方法中萨驶,入?yún)⑹莟arget,作用于class A上就是 A 艇肴,我們知道篡撵,在ES中一切都是對象,class 是ES6以后的一個面向?qū)ο蟮恼Z法糖豆挽,在這里的A本質(zhì)也就是一個function,在新建實例的時候作為構(gòu)造函數(shù)調(diào)用券盅。這里通過target.prototype我們也能獲得這個類的原型帮哈。這樣我們就可以對這個類進行修改了。值得注意的是锰镀,
裝飾器是在編譯期間發(fā)生的娘侍,這個時候類的實例還沒有生成,因此裝飾器無法直接對類的實例進行修改泳炉。但是可以間接的通過修改類的原型影響實例
這樣的修飾器意義不大憾筏,我們要應對更多的情況,因此可以給修飾器加上參數(shù)花鹅,或者叫做'注解'
@modifyClass('param')
class A {
}
function modifyClass(param) {
return target => {
target.prototype.extraProp = param
}
}
之所以要給注解添加引號氧腰,是因為注解的概念是要進行元數(shù)據(jù)的修改,而這里僅僅是動態(tài)改變原型上的屬性刨肃。要進行元數(shù)據(jù)的修改古拴,我們需要利用反射Reflect。 ES6提供的Refelct并不滿足修改元數(shù)據(jù)真友,我們要額外引入一個庫reflect-metadata
import 'reflect-metadata'
@modifyClass('param')
class A {
}
function modifyClass(param) {
return target => {
Reflect.defineMetadata(Symbol.for('META_PARAM'), param, target.prototype)
}
}
這個時候就是真正的注解了黄痪,我們通過裝飾器和Reflect對要修飾的類注入了元數(shù)據(jù),注意我們這里是注入到target.prototype
盔然,類的實例上桅打。因為不同的實例是獲得的不同的數(shù)據(jù),因此不能注入到target上愈案。
Reflect.defineMetadata
方法第一個入?yún)⒖梢允莝tring 類型或者 symbol 類型挺尾。建議使用symbol類型,這樣避免被覆蓋掉。而這里的param可以是任意類型刻帚。我們不僅能在類上定義元數(shù)據(jù)潦嘶,也可以在類的屬性上定義
Relect.defineMetadata(metadataKey: any, metadataValue: any, target: any, propertyKey)
接下來我們就可以在任意的地方通過Reflect.getMetadata(metadataKey, target)
獲取元數(shù)據(jù)了。
反射給了我們在類及其屬性、方法掂僵、入?yún)⑸洗鎯ψx取數(shù)據(jù)的能力
類及其實例并不能感知或者修改存取在類上元數(shù)據(jù)航厚,但是我們可以通過裝飾器和注解在編譯時動態(tài)的修改它們的行為,即我們寫了一個函數(shù)去修改函數(shù)锰蓬,我們把這樣的行為稱作元編程幔睬,接下來我們看看裝飾器作用在屬性、方法芹扭、入?yún)⑸?/p>
@modifyClass('new Prop')
class A {
@modifyProp type: string
name: string
constructor (name) {
this.name = name
}
@modifyMethod
say (@modifyParam word) {
let str = Reflect.getMetadata(key, this)
console.log(str)
}
}
// 在裝飾類的裝飾器上獲得target(類)是類本身
// 在裝飾屬性麻顶、方法、入?yún)⑸汐@得的target的是類的原型target(屬性舱卡、方法辅肾、入?yún)? === target(類).prototype
function modifyClass (name) {
return (target) => {
target.prototype.extra = name
}
}
function modifyProp (target, propertyKey) {
// 修改屬性
console.log(target)
console.log(propertyKey)
target[propertyKey] = 'modfiyed by decorator'
}
// 我們在 ts 版本的 vuex 裝飾器中看到的 @state('key') key 等價于
// function state (key) {
// return (target, propertyKey) => {
// target[propertyKey] = target.$store.state[key]
// }
// }
// 修飾方法
// descriptor對象原來的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
function modifyMethod (target, propertyKey, descriptor) {
Reflect.defineMetadata(key, 'Hello Reflect',target)
const fun = descriptor.value
descriptor.value = function () {
console.log(this) // 運行時確定因此這里是的 this 指向?qū)嵗摹H绻@里是箭頭函數(shù)轮锥,this則指向undefined
return fun.apply(this, arguments)
}
}
// 修飾入?yún)?// index 是這個參數(shù)的順序
function modifyParam (target, propertyKey, index) {
console.log(target)
console.log(propertyKey)
console.log(index)
}
通過裝飾器實現(xiàn)一個切面
class A {
say()
}
function AOP(type, func) {
return (target, propetyKey, descriptor) => {
let oldMethod = descriptor.value
if (type == 'BEFORE') {
descripotor.value = function () {
fun(...arguments)
return oldMethod.apply(this, arguments)
}
} else if (type == 'AFTER') {
descripotor.value = function () {
let result = oldMethod.apply(this, arguments)
fun(...arguments)
return result
}
}
}
}
注意矫钓,這里的切點只能是同步函數(shù),如果要異步的函數(shù)需要進行額外的處理舍杜,是否為異步函數(shù)同樣可以通過注解表明新娜。這里也只能用函數(shù)表達式,不能使用箭頭函數(shù)既绩,否則會造成this的丟失概龄。
總結(jié)
- 裝飾器提供了對類的屬性、方法饲握、入?yún)⑿薷牡哪芰λ蕉牛菃为毧垦b飾器是不夠的,還要通過注解配合互拾,這樣才能動態(tài)的修改原來的表現(xiàn)行為歪今。因此我們可以封裝一些常用的裝飾器方法,達到復用的能力颜矿。但要切記寄猩,裝飾器的行為是發(fā)生在編譯時
- 這里的裝飾器修飾是在TS上完成的,在不涉及Reflec時TS和ES的目前表現(xiàn)一致骑疆。那么在涉及Reflect時的表現(xiàn)是什么樣的呢田篇?我也不知道啊o_O。并且TS和ES的裝飾器是有不同的箍铭,未來的版本可能也會發(fā)生根本的改變泊柬。
以上都是我瞎編的