zoukankan      html  css  js  c++  java
  • javascript装饰器模式

    装饰器模式

    什么是装饰器

    原名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的实现等

  • 相关阅读:
    Mac上TexStudio无法显示中文字符的问题
    python中import与from方法总结
    Jupter Notebook 使用技法
    python把列表(list)传给函数形参时的问题剖析
    Spyder常用快捷键
    用Tinkercad学arduino之 多喇叭发声
    用Tinkercad学arduino之 播放旋律
    用Tinkercad学arduino之 音调键盘 按键改变音调
    用Tinkercad学arduino之 伺服电机摆动
    用Tinkercad学arduino之 读取电位器模拟输入
  • 原文地址:https://www.cnblogs.com/coolslider/p/8459729.html
Copyright © 2011-2022 走看看