美文网首页
以koa2为例, 探索Javascript装饰器使用

以koa2为例, 探索Javascript装饰器使用

作者: 朝西的生活 | 来源:发表于2018-09-18 22:05 被阅读0次

什么是Decoretor

  • Decoretor是在声明阶段实现类与类的成员注解的一种语法, 其本质就是一种特殊的函数
  • Decoretor有两种使用方法, 一种是修饰类, 另一种是修饰类的成员, 其中, 类成员, 包括类属性成员和类方法成员
  • 如果一个类和类的方法都是用了Decorator,类方法的Decorator优先于类的Decorator执行
@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大致长这样, @log是修饰类的装饰器, @readonly是修饰类的属性的装饰器, @validate是修饰类的方法的装饰器,

修饰类的装饰器

修饰类的装饰器, 其参数就是被修饰的类, 在上面例子中, @log的方法接受的参数即Numberic

function log() {
    const desc = Object.getOwnPropertyDescriptors(target.prototype);
    for (const key of Object.keys(desc)) {
        if(key === 'constructor'){
            continue;
        }
        // 获取属性, 
        //这里需要注意, 在es6中, 类的属性不一定是一个字符串常量, 
        const func = desc[key].value;
        // 只关心类的方法
        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参数有三个, 分别是类的实例对象, 属性名称, 以及该类成员的描述符

function readonly(target, key, desc){
    // 我们只是希望该属性不可修改, 因此只需要将描述符的writable属性设为false
    desc.writable = false;
}

修饰类方法的Decortor

类方法成员的装饰器参数同类属性的装饰器一样, 接受类的实例对象, 属性名称, 以及该类成员的描述符三个参数

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

带参数的装饰器

带参数的装饰器需在外层包裹一层函数接受参数

// Decorator不传参
function Controller(target) {

}

// Decorator传参
function Controller(params) {
 return function (target) {

 }
}

如果Decorator是传参的,即使params有默认值,在调用时必须带上括号,即:

@Controller()
class Hello{

}

koa中用装饰器描述应用路由

先看看传统的koa写法(不带装饰器)

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;

可以看出, 使用装饰器之后的应用路由更简洁,

  • 首先我们先实现修饰controller类的装饰器, 该装饰器接受一个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) {
      // 排除类的构造方法
      if (v !== 'constructor') {
        let fn = functions[v].value
        fn(router)
      }
    }
    // 返回router实例, 供类成员的装饰器使用
    return router
  }
}

接着, 我们再实现类的成员的装饰器

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

说句题外话: 在koa中, 我们通过ctx.body = xxx来实现接口返回数据.
有了上面装饰器, 我们可以在控制函数中直接return 我们想要返回的数据, 然后通过装饰器再接收并赋值给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); // 这里接收控制函数中返回值
          if(res !== undefined){  // 默认情况下, 函数返回undefined, 因此这里需要过滤一下
            ctx.body = res;
          }
        })
      }
    }
  }
})

使用装饰器很简单, 新建一个class 为其加上router装饰器, 参数为koa-router的config

const router = require('../utils/router'); // 导入上一步的装饰器

@router() // 装饰控制类
class HelloController {
  @router.get('/get/:id') // 装饰方法
  async hello(ctx){ // 定义控制函数
    const {params, query, body} = ctx;
    return ctx // 直接return需要返回的数据, 在上一步装饰器中接收
  }

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

module.exports = HelloController;

这里@router()以及@router.get('/get/:id')即我们上面步骤定义的类的装饰器.

然后我们在app.js里面导入, 使用方式与koa-router一样

import HelloController from './controller/hello';

// 添加的装饰器默认返回KoaRouter实例

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

相关文章

网友评论

      本文标题:以koa2为例, 探索Javascript装饰器使用

      本文链接:https://www.haomeiwen.com/subject/luuvnftx.html