javascript装饰器模式

装饰器模式

什么是装饰器react

原名decorator 被翻译为装饰器 能够理解为装饰 修饰 包装等意数据库

现实中的做用

一间房子经过装饰能够变得更华丽,功能更多
相似一部手机能够单独使用 可是不少人都愿意家个保护套来防摔。。。npm

js中的做用

装饰器能够说是解决了不一样类之间共享方法的问题(能够看作是弥补继承的不足)。redux

A Python decorator is a function that takes another function, extending the behavior of the latter function without explicitly modifying it.
这句话能够说是对装饰器的很是漂亮的解释。api

在将来的 JavaScript 中也引入了这个概念,而且 babel 对他有很好的支持。若是你是一个疯狂的开发者,就能够借助 babel 大胆使用它。浏览器

环境准备

装饰器目前在浏览器或者 Node 中都暂时不支持,须要借助 babel 转化为可支持的版本babel

安装 babelapp

按照官网的 说明 安装:koa

npm install --save-dev babel-cli babel-preset-env

在 .babelrc 中写入:async

{
  "presets": ["env"]
}

按照说明,安装 babel-plugin-transform-decorators-legacy 插件:

npm install babel-plugin-transform-decorators-legacy --save-dev

.babelrc :

{
  "presets": ["env"],
  "plugins": ["transform-decorators-legacy"]
}

这样准备工做就完成了。

开始

先看看一个装饰器的写法

class Boy{
  @run
  speak (){
    console.log('I can speak')
  }
}
function run () {
  console.log('I can run')
}

let tj =  new Boy()
tj.speak()

// I can run 
// I can speak

@run 就是给类属性方法(speak)加的一个装饰器(其实也就是一个函数) 扩展了类Boy的speak(在讲话的同时跑步)

装饰器不只能够装饰类的方法还能够装饰类(可是不能够装饰函数,由于函数存在变量提高)
装饰器函数接受3个参数 分别是装饰的对象,装饰的属性,装饰属性的描述

class Boy{
  @run
  speak (){
    console.log('I can speak')
  }
}
function run (target,key,descripter) {
  console.log(target,key,descripter)
}

let tj =  new Boy()
tj.speak()
// Boy {} 'speak' { value: [Function: speak],
  writable: true,
  enumerable: false,
  configurable: true }
I can speak

再来看一个例子

class Math {
  @log
  add(a, b) {
    return a + b
  }
}

function log(target, name, descriptor) {
  var oldValue = descriptor.value

  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments)
    return oldValue.apply(target, arguments)
  }

  return descriptor
}

const math = new Math()

// passed parameters should get logged now
math.add(2, 4)
// Calling add with { '0': 2, '1': 4 }

至关于在原来的add方法上扩展了一个console.log的功能,并无改变原来的功能 (咱们能够取到参数 并改变他)

还能够经过装饰器传递参数

function log(num) {
  return function(target, name, descriptor) {
    var oldValue = descriptor.value
    let _num = num || 0
    descriptor.value = (...arg) => {
      arg[0] += _num
      console.log(`Calling${target}, ${name} with`, arg)
      return oldValue.apply(target, arg)
    }
    return descriptor
  }
}

class Math {
  constructor(a = 3, b = 4) {
    this.add(a, b)
  }
  @log(100)
  add(a, b) {
    return a + b
  }
}

const math = new Math()

console.log(math)
console.log(math.add(9,1))

咱们用装饰器来装饰koa-router

咱们想给koa-router扩展更多的功能,而且是可读性维护性和代码的优雅性都很好的好比:

export default class MovieRouter{
  @get('/api/v0/movie')
  @auth()
  @log()
  ...
}

让路由在真正处理业务的时候先作些其余的准备工做(如上先验证用户是否登陆,而后输出日志)
就以上,咱们先简单实现一下

const Koa = require('koa')
const app = new Koa()
const {connect} = require('../db/index')
const mongoose = require('mongoose')
const Shijue = mongoose.model('Shijue')
const Router = require('koa-router')
const router = new Router()

// 链接数据库
void (async () => {
  await connect()
})()

class Route {
  constructor() {
    this.app = app
    this.router = router
  }

  init() {
    routerMap.map(item=>{
      router[item.method](item.path, item.callback)
    })
    app.use(router.routes())
    app.use(router.allowedMethods())
  }
}

var routerMap = []

function get(path) {
  return function(target, key, descriptor) {
    routerMap.push({path, target, method: 'get', callback: target[key]})
    return descriptor
  }
}
var logTimes = 0
function log() {
  return function(target, key, descriptor) {
    app.use(async function(ctx, next) {
      logTimes++
      console.time(`${logTimes}: ${ctx.method} - ${ctx.url}`)
      await next()
      console.timeEnd(`${logTimes}: ${ctx.method} - ${ctx.url}`)
      return descriptor
    })
  }
}

class ShijueRouter {
  @get('/api')
  @log()
  async getShijue(ctx, next) {
    // await ...
    return (ctx.body = {code: 0, data: 'shijue'})
  }
}

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

async function start() {
  var r = new Route()
  r.init()
  app.listen(3001, function(err) {
    if (err) {
      console.log(err)
    } else {
      console.log('启动成功:3001')
    }
  })
}
start()

代码比较粗糙能够提炼分离

还有如react-redux的实现等

相关文章
相关标签/搜索