以koa2為例, 探索Javascript裝飾器使用

什么是Decoretor

  • Decoretor是在聲明階段實(shí)現(xiàn)類與類的成員注解的一種語(yǔ)法, 其本質(zhì)就是一種特殊的函數(shù)
  • Decoretor有兩種使用方法, 一種是修飾類, 另一種是修飾類的成員, 其中, 類成員, 包括類屬性成員和類方法成員
  • 如果一個(gè)類和類的方法都是用了Decorator权均,類方法的Decorator優(yōu)先于類的Decorator執(zhí)行
@Controller
class Hello{

}

// 等同于

Controller(Hello)
@log
class Numberic {
    @readoly PI = Math.PI;
    
    @validate
    add(...params) {
        return nums.reduce((P, n) => P + n, 0)
    }
}

在js中, decoretor大致長(zhǎng)這樣, @log是修飾類的裝飾器, @readonly是修飾類的屬性的裝飾器, @validate是修飾類的方法的裝飾器,

修飾類的裝飾器

修飾類的裝飾器, 其參數(shù)就是被修飾的類, 在上面例子中, @log的方法接受的參數(shù)即Numberic

function log() {
    const desc = Object.getOwnPropertyDescriptors(target.prototype);
    for (const key of Object.keys(desc)) {
        if(key === 'constructor'){
            continue;
        }
        // 獲取屬性, 
        //這里需要注意, 在es6中, 類的屬性不一定是一個(gè)字符串常量, 
        const func = desc[key].value;
        // 只關(guān)心類的方法
        if('function' === typeof func){
            // 重新定義類的原方法, 
            Object.defineProperty(target.prototype, key, {
                value(...args) {
                    console.log('before: ' + key);
                    const ret = func.apply(this.args);
                    console.log('after: ' + key);
                    return ret;
                }
            })
        }
    }
}

修飾類成員的Decoretor

修飾類成員的Decoretor參數(shù)有三個(gè), 分別是類的實(shí)例對(duì)象, 屬性名稱, 以及該類成員的描述符

function readonly(target, key, desc){
    // 我們只是希望該屬性不可修改, 因此只需要將描述符的writable屬性設(shè)為false
    desc.writable = false;
}

修飾類方法的Decortor

類方法成員的裝飾器參數(shù)同類屬性的裝飾器一樣, 接受類的實(shí)例對(duì)象, 屬性名稱, 以及該類成員的描述符三個(gè)參數(shù)

function validate(target, key, desc){
    const func = desc.value;
    desc.value = function(...args){
        // 遍歷入?yún)?        for(let num of args){
            if('number' !== typeof num){
                throw new Error(`"${num}" is not a number`);
            }
        }
        return func.apply(this, args);
    }
}

帶參數(shù)的裝飾器

帶參數(shù)的裝飾器需在外層包裹一層函數(shù)接受參數(shù)

// Decorator不傳參
function Controller(target) {

}

// Decorator傳參
function Controller(params) {
 return function (target) {

 }
}

如果Decorator是傳參的腹缩,即使params有默認(rèn)值鹏氧,在調(diào)用時(shí)必須帶上括號(hào)尸折,即:

@Controller()
class Hello{

}

koa中用裝飾器描述應(yīng)用路由

先看看傳統(tǒng)的koa寫(xiě)法(不帶裝飾器)

var Koa = require('koa');
var Router = require('koa-router');

var app = new Koa();
var router = new Router();

router.get('/', async (ctx, next) => {
 // ctx.router available
});

app
 .use(router.routes())
 .use(router.allowedMethods());

使用裝飾器之后的代碼:

const router = require('../router');

@router()
class Home {
  @router.get('/')
  async home(ctx) {
    await ctx.render('index');
  }
}

module.exports = Home;

可以看出, 使用裝飾器之后的應(yīng)用路由更簡(jiǎn)潔,

  • 首先我們先實(shí)現(xiàn)修飾controller類的裝飾器, 該裝飾器接受一個(gè)config, 作用同koa-router的config
const KoaRouter = require("koa-router");

function router(config = {}) {
  const router = new KoaRouter(config)
  return function (target) {
    const functions = Object.getOwnPropertyDescriptors(target.prototype)
    for (let v in functions) {
      // 排除類的構(gòu)造方法
      if (v !== 'constructor') {
        let fn = functions[v].value
        fn(router)
      }
    }
    // 返回router實(shí)例, 供類成員的裝飾器使用
    return router
  }
}

接著, 我們?cè)賹?shí)現(xiàn)類的成員的裝飾器

const methods = ['get', 'post', 'put', 'delete', 'options', 'head', 'patch'];
methods.forEach(method => {
  router[method] = url => {
    return function (target, name, descriptor) {
      let fn = descriptor.value
      descriptor.value = (router) => {
        router[method](url, async(ctx, next) => {
          await fn(ctx, next)
        })
      }
    }
  }
})

說(shuō)句題外話: 在koa中, 我們通過(guò)ctx.body = xxx來(lái)實(shí)現(xiàn)接口返回?cái)?shù)據(jù).
有了上面裝飾器, 我們可以在控制函數(shù)中直接return 我們想要返回的數(shù)據(jù), 然后通過(guò)裝飾器再接收并賦值給ctx.body.

const methods = ['get', 'post', 'put', 'delete', 'options', 'head', 'patch'];
methods.forEach(method => {
  router[method] = url => {
    return function (target, name, descriptor) {
      let fn = descriptor.value
      descriptor.value = (router) => {
        router[method](url, async(ctx, next) => {
          const res = await fn(ctx, next); // 這里接收控制函數(shù)中返回值
          if(res !== undefined){  // 默認(rèn)情況下, 函數(shù)返回undefined, 因此這里需要過(guò)濾一下
            ctx.body = res;
          }
        })
      }
    }
  }
})

使用裝飾器很簡(jiǎn)單, 新建一個(gè)class 為其加上router裝飾器, 參數(shù)為koa-router的config

const router = require('../utils/router'); // 導(dǎo)入上一步的裝飾器

@router() // 裝飾控制類
class HelloController {
  @router.get('/get/:id') // 裝飾方法
  async hello(ctx){ // 定義控制函數(shù)
    const {params, query, body} = ctx;
    return ctx // 直接return需要返回的數(shù)據(jù), 在上一步裝飾器中接收
  }

  @router.post('/post')
  async post(ctx){
    return ctx.request.body;
  }
}

module.exports = HelloController;

這里@router()以及@router.get('/get/:id')即我們上面步驟定義的類的裝飾器.

然后我們?cè)?code>app.js里面導(dǎo)入, 使用方式與koa-router一樣

import HelloController from './controller/hello';

// 添加的裝飾器默認(rèn)返回KoaRouter實(shí)例

app.use(HelloController.routes()).use(HelloController.allowedMethods());
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌收恢,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囚枪,死亡現(xiàn)場(chǎng)離奇詭異派诬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)链沼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)默赂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人括勺,你說(shuō)我怎么就攤上這事缆八。” “怎么了疾捍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵奈辰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我乱豆,道長(zhǎng)奖恰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任宛裕,我火速辦了婚禮瑟啃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揩尸。我一直安慰自己蛹屿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布岩榆。 她就那樣靜靜地躺著错负,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勇边。 梳的紋絲不亂的頭發(fā)上犹撒,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音粒褒,去河邊找鬼识颊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛怀浆,可吹牛的內(nèi)容都是我干的谊囚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼执赡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼镰踏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起沙合,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奠伪,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后首懈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體绊率,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年究履,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滤否。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡最仑,死狀恐怖藐俺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泥彤,我是刑警寧澤欲芹,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吟吝,受9級(jí)特大地震影響菱父,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剑逃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一浙宜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炕贵,春花似錦梆奈、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鳖轰,卻和暖如春清酥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕴侣。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工焰轻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昆雀。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓辱志,卻偏偏與公主長(zhǎng)得像蝠筑,于是被迫代替她去往敵國(guó)和親揩懒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理已球,服務(wù)發(fā)現(xiàn)臣镣,斷路器,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法智亮,類相關(guān)的語(yǔ)法忆某,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法阔蛉,異常的語(yǔ)法弃舒,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,632評(píng)論 18 399
  • 20171128星期二 天氣晴 北風(fēng)乎乎地吹,都不想出門(mén)…下午下班回到家馍忽,做完飯后孩子們才說(shuō)他們已經(jīng)吃了…...
    璇戎爸爸閱讀 143評(píng)論 0 0
  • “被騙的永遠(yuǎn)是雛兒棒坏,后來(lái)我混江湖久了,變成老鴇遭笋,就幾乎再?zèng)]見(jiàn)過(guò)盯上我的騙子坝冕。” 二雙換了她的簽名瓦呼。 我趕緊給她發(fā)了...
    木不周閱讀 356評(píng)論 2 1
  • 走在路上喂窟,牽著兒子的小手, 即使?fàn)恐盅氪彩潜嫩Q蹦跶的磨澡, 這個(gè)時(shí)候我喜歡稍微緊握下,再次確認(rèn)手里握著的小手质和,肉軟...
    澄乎乎閱讀 755評(píng)論 2 9