先看一段代碼
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 路由類的寫(xiě)法,注意到在其中,使用了@Paht @Get
的寫(xiě)法, 并且在入?yún)⒅幸灿?code>@PathParam('id') id: number這樣的寫(xiě)法。這就是裝飾器。其中 @Path('/api')
中的API是這個(gè)裝飾器的入?yún)ⅲ谶@里是注解,因?yàn)檫@個(gè)框架通過(guò)Reflect.defineMetadata
將這個(gè)入?yún)?xiě)入到了該方法中。在搞清這些復(fù)雜的概念之前,我們先弄明白兩個(gè)最基礎(chǔ)的概念
裝飾器和注解
- 裝飾器(Decorator) 僅提供定義劫持,能夠?qū)︻惣捌浞椒ā⒎椒ㄈ雲(yún)ⅰ傩缘亩x并沒(méi)有提供任何附加元數(shù)據(jù)的功能。
- 注解(Annotation) 僅提供附加元數(shù)據(jù)支持,并不能實(shí)現(xiàn)任何操作。需要另外的 Scanner 根據(jù)元數(shù)據(jù)執(zhí)行相應(yīng)操作。
注意到裝飾器是對(duì)類及其方法、入?yún)ⅰ傩孕袨榈男薷模⒔庵皇翘砑釉獢?shù)據(jù),不能修改行為。
在實(shí)際的開(kāi)發(fā)過(guò)程中,我們通過(guò)注解添加元數(shù)據(jù),裝飾器再獲取這些元數(shù)據(jù)完成對(duì)類或者方法的修改,下面開(kāi)始對(duì)修改一個(gè)類
實(shí)際操作
class A {
}
首先我們聲明了一個(gè)什么也沒(méi)有類,接著我們聲明第一個(gè)修改器方法,并且作用在類上
@modifyClass
class A {
}
function modifyClass(target: any) {
target.prototype.extraProp = 'decorator'
}
在裝飾器的方法中,入?yún)⑹莟arget,作用于class A上就是 A ,我們知道,在ES中一切都是對(duì)象,class 是ES6以后的一個(gè)面向?qū)ο蟮恼Z(yǔ)法糖,在這里的A本質(zhì)也就是一個(gè)function,在新建實(shí)例的時(shí)候作為構(gòu)造函數(shù)調(diào)用。這里通過(guò)target.prototype我們也能獲得這個(gè)類的原型。這樣我們就可以對(duì)這個(gè)類進(jìn)行修改了。值得注意的是,
裝飾器是在編譯期間發(fā)生的,這個(gè)時(shí)候類的實(shí)例還沒(méi)有生成,因此裝飾器無(wú)法直接對(duì)類的實(shí)例進(jìn)行修改。但是可以間接的通過(guò)修改類的原型影響實(shí)例
這樣的修飾器意義不大,我們要應(yīng)對(duì)更多的情況,因此可以給修飾器加上參數(shù),或者叫做'注解'
@modifyClass('param')
class A {
}
function modifyClass(param) {
return target => {
target.prototype.extraProp = param
}
}
之所以要給注解添加引號(hào),是因?yàn)樽⒔獾母拍钍且M(jìn)行元數(shù)據(jù)的修改,而這里僅僅是動(dòng)態(tài)改變?cè)蜕系膶傩浴RM(jìn)行元數(shù)據(jù)的修改,我們需要利用反射Reflect。 ES6提供的Refelct并不滿足修改元數(shù)據(jù),我們要額外引入一個(gè)庫(kù)reflect-metadata
import 'reflect-metadata'
@modifyClass('param')
class A {
}
function modifyClass(param) {
return target => {
Reflect.defineMetadata(Symbol.for('META_PARAM'), param, target.prototype)
}
}
這個(gè)時(shí)候就是真正的注解了,我們通過(guò)裝飾器和Reflect對(duì)要修飾的類注入了元數(shù)據(jù),注意我們這里是注入到target.prototype
,類的實(shí)例上。因?yàn)椴煌膶?shí)例是獲得的不同的數(shù)據(jù),因此不能注入到target上。
Reflect.defineMetadata
方法第一個(gè)入?yún)⒖梢允莝tring 類型或者 symbol 類型。建議使用symbol類型,這樣避免被覆蓋掉。而這里的param可以是任意類型。我們不僅能在類上定義元數(shù)據(jù),也可以在類的屬性上定義
Relect.defineMetadata(metadataKey: any, metadataValue: any, target: any, propertyKey)
接下來(lái)我們就可以在任意的地方通過(guò)Reflect.getMetadata(metadataKey, target)
獲取元數(shù)據(jù)了。
反射給了我們?cè)陬惣捌鋵傩浴⒎椒ā⑷雲(yún)⑸洗鎯?chǔ)讀取數(shù)據(jù)的能力
類及其實(shí)例并不能感知或者修改存取在類上元數(shù)據(jù),但是我們可以通過(guò)裝飾器和注解在編譯時(shí)動(dòng)態(tài)的修改它們的行為,即我們寫(xiě)了一個(gè)函數(shù)去修改函數(shù),我們把這樣的行為稱作元編程,接下來(lái)我們看看裝飾器作用在屬性、方法、入?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'
}
// 我們?cè)?ts 版本的 vuex 裝飾器中看到的 @state('key') key 等價(jià)于
// function state (key) {
// return (target, propertyKey) => {
// target[propertyKey] = target.$store.state[key]
// }
// }
// 修飾方法
// descriptor對(duì)象原來(lái)的值如下
// {
// 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) // 運(yùn)行時(shí)確定因此這里是的 this 指向?qū)嵗摹H绻@里是箭頭函數(shù),this則指向undefined
return fun.apply(this, arguments)
}
}
// 修飾入?yún)?// index 是這個(gè)參數(shù)的順序
function modifyParam (target, propertyKey, index) {
console.log(target)
console.log(propertyKey)
console.log(index)
}
通過(guò)裝飾器實(shí)現(xiàn)一個(gè)切面
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
}
}
}
}
注意,這里的切點(diǎn)只能是同步函數(shù),如果要異步的函數(shù)需要進(jìn)行額外的處理,是否為異步函數(shù)同樣可以通過(guò)注解表明。這里也只能用函數(shù)表達(dá)式,不能使用箭頭函數(shù),否則會(huì)造成this的丟失。
總結(jié)
- 裝飾器提供了對(duì)類的屬性、方法、入?yún)⑿薷牡哪芰Γ菃为?dú)靠裝飾器是不夠的,還要通過(guò)注解配合,這樣才能動(dòng)態(tài)的修改原來(lái)的表現(xiàn)行為。因此我們可以封裝一些常用的裝飾器方法,達(dá)到復(fù)用的能力。但要切記,裝飾器的行為是發(fā)生在編譯時(shí)
- 這里的裝飾器修飾是在TS上完成的,在不涉及Reflec時(shí)TS和ES的目前表現(xiàn)一致。那么在涉及Reflect時(shí)的表現(xiàn)是什么樣的呢?我也不知道啊o_O。并且TS和ES的裝飾器是有不同的,未來(lái)的版本可能也會(huì)發(fā)生根本的改變。
以上都是我瞎編的