1. 程式人生 > >javascript裝飾器模式

javascript裝飾器模式

目前 edm ould list model 開發 cti on() 概念

裝飾器模式

什麽是裝飾器

原名decorator 被翻譯為裝飾器 可以理解為裝飾 修飾 包裝等意

現實中的作用

一間房子通過裝飾可以變得更華麗,功能更多
類似一部手機可以單獨使用 但是很多人都願意家個保護套來防摔。。。

js中的作用

裝飾器可以說是解決了不同類之間共享方法的問題(可以看做是彌補繼承的不足)。

A Python decorator is a function that takes another function, extending the behavior of the latter function without explicitly modifying it.
這句話可以說是對裝飾器的非常漂亮的解釋。

在未來的 JavaScript 中也引入了這個概念,並且 babel 對他有很好的支持。如果你是一個瘋狂的開發者,就可以借助 babel 大膽使用它。

環境準備

裝飾器目前在瀏覽器或者 Node 中都暫時不支持,需要借助 babel 轉化為可支持的版本

安裝 babel

按照官網的 說明 安裝:

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

在 .babelrc 中寫入:

{
  "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的實現等

javascript裝飾器模式