装饰器设计模式

151 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

前情提要

装饰器模式又称修饰器模式,目前babel已经完全支持了对其的转码,所以我们可以在项目里开始使用了。并且在Typescript中就有大量的使用装饰器模式。

什么是装饰器模式?

  • 用一句话解释就是允许我们给一个对象拓展新的功能,但是又不改变原对象,属于非侵入式的拓展。
  • 跟java的注解形似,但是功能还是有较大差别的。
  • 打一个不恰当的比喻,就像我们玩的王者荣耀,每个英雄都是独立的对象,每个装备就是一个个装饰器,英雄购买了装备就只是在身上进行装备,而对英雄本身没有改变,但是其攻击等属性却得到了提升。
  • 装饰器可以为类进行拓展,可以为方法进行拓展,也可以给属性进行拓展。
  • 属于ES7中的新特性

基于装饰器模式实现对类的拓展

  • 简易的代码实现
    class Hero {
        @addAttrs
        speak() {
            console.log('我是一个英雄')
        }
    }
    function addAttrs(target, key, descripteor) {
        console.log(target, key, descripter);
        console.log('我为这个英雄增加100点暴击伤害');
    }
    const newHero = new Hero();
    newHero.speak();
    
  • 如果把这段代码直接复制到浏览器或者Nodejs环境执行的话你会发现报错了,无法识别@这个符号。因为装饰器是ES7的新特性,所以现在的浏览器或者Nodejs环境还没有适配。
  • 此时我们需要使用babel这个神器帮我们将ES7的语法转换成ES5的代码。

使用babel将装饰器模式进行转译

  1. 将咱们写的代码所在的js文件移动到一个新的文件夹中
  2. 执行npm init命令进行初始化,让填的信息填好。此时会新增一个package.json文件
  3. 创建一个.babelrc文件
  4. 编辑.babelrc文件:
{
  "presets": ["env"],
  "plugins": ["transform-decorators-legacy"]
}
  1. 打开终端或者cmd命令窗口,cd到该目录,然后执行命令:npm i -S babel-cli babel-preset-env babel-plugin-transform-decorators-legacy
  2. 在package.json文件中会发现这几个插件的信息都罗列在里面了。此时我们需要在script中新增一个启动命令: “build”: "babel ./index.js -o build/index.js"
    • 此处的index.js是我创建的js文件名字,如果你起了别的名字,那么此处就需要做更改。
  3. 此时需要在文件夹中新增build文件夹,用来存放编译后的文件。
  4. 然后我们执行:npm run build,之后我们就可以在build文件夹中看到编译后的js文件了。
  5. 此时我们就可以运行这个编译后的文件了。

上面简易代码运行的分析

  • 这是代码运行后的结果

    image.png

  • 从浏览器中我们发现在执行speak方法之前先执行的我们给speak拓展的方法。

  • 而作为装饰器方法接收了三个参数:

    • target:我们拓展的方法所在的类实例
    • key: 我们拓展的方法的方法名
    • descriptor: 我们拓展的方法的属性

上面实现的是没有传参的情况,那么如果有参数怎么办呢?

class Hero {
  @addAttrs('攻击加1000')
  speak(attrs) {
      attrs.forEach((item) => {
          console.log(item)
      })
  }
}
function addAttrs(attr) {
  return function (target, key, descriptor) {
      const oldValue = descriptor.value; // 之前的speak方法
      descriptor.value = function(...arg) { // 此处重写上面方法,然后在重写的方法里面进行调用之前的方法
          arg[0].push(attr);
          return oldValue.call(target, ...arg)
      }
  }
  
}
const newHero = new Hero();
newHero.speak(['法术强度加1000', '物理穿透加100000']);
  • 使用上面转译方法进行转译之后的运行结果: image.png