TS從裝飾器到注解到元編程

先看一段代碼

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é)

  1. 裝飾器提供了對類的屬性、方法饲握、入?yún)⑿薷牡哪芰λ蕉牛菃为毧垦b飾器是不夠的,還要通過注解配合互拾,這樣才能動態(tài)的修改原來的表現(xiàn)行為歪今。因此我們可以封裝一些常用的裝飾器方法,達到復用的能力颜矿。但要切記寄猩,裝飾器的行為是發(fā)生在編譯時
  2. 這里的裝飾器修飾是在TS上完成的,在不涉及Reflec時TS和ES的目前表現(xiàn)一致骑疆。那么在涉及Reflect時的表現(xiàn)是什么樣的呢田篇?我也不知道啊o_O。并且TS和ES的裝飾器是有不同的箍铭,未來的版本可能也會發(fā)生根本的改變泊柬。

以上都是我瞎編的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诈火,隨后出現(xiàn)的幾起案子兽赁,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刀崖,死亡現(xiàn)場離奇詭異惊科,居然都是意外死亡,警方通過查閱死者的電腦和手機亮钦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門馆截,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜂莉,你說我怎么就攤上這事蜡娶。” “怎么了映穗?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵窖张,是天一觀的道長。 經(jīng)常有香客問我蚁滋,道長荤堪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任枢赔,我火速辦了婚禮,結(jié)果婚禮上拥知,老公的妹妹穿的比我還像新娘踏拜。我一直安慰自己,他們只是感情好低剔,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布速梗。 她就那樣靜靜地躺著,像睡著了一般襟齿。 火紅的嫁衣襯著肌膚如雪姻锁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天猜欺,我揣著相機與錄音位隶,去河邊找鬼。 笑死开皿,一個胖子當著我的面吹牛涧黄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赋荆,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼笋妥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窄潭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澄暮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躏惋,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年嫁赏,在試婚紗的時候發(fā)現(xiàn)自己被綠了其掂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡潦蝇,死狀恐怖款熬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情攘乒,我是刑警寧澤贤牛,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站则酝,受9級特大地震影響殉簸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沽讹,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一般卑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧爽雄,春花似錦蝠检、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乘盖,卻和暖如春焰檩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背订框。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工析苫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人穿扳。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓藤违,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纵揍。 傳聞我的和親對象是個殘疾皇子顿乒,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來 What:A...
    zlcook閱讀 29,120評論 15 116
  • 前言,本來只是想研究一下注解的泽谨,不過發(fā)現(xiàn)璧榄,要懂注解先得懂反射特漩,別問我為什么,你可以自己試試 JAVA反射 主要是指...
    justCode_閱讀 1,218評論 2 9
  • 前言 人生苦多骨杂,快來 Kotlin 涂身,快速學習Kotlin! 什么是Kotlin搓蚪? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,146評論 9 118
  • 今天: 1.前橋 1.1設備調(diào)試工作計劃 1.2現(xiàn)場工作梳理 1.3代碼:底層蛤售,返修代碼;底層妒潭,下線代碼悴能;OPC通...
    先胖不算胖閱讀 59評論 0 0
  • 度量品優(yōu)比月明,無疆父愛堪星辰雳灾。 精言暖語入我心漠酿,高德致行化我思。 萬千心念伴我長谎亩,珍時惜間陪您老炒嘲。 得此父如獲至...
    萬愛閱讀 188評論 0 1