JS 设计模式之装饰者模式

94 阅读5分钟

我正在参加「掘金·启航计划」

在项目中看到有人使用装饰器,对于我这种好奇心比较强烈的人,特别是没有接触过这项技术的人,好奇心就上来了,自己也学习一下,并在项目中响应的场景进行了实战,感觉这种开发模式确实不错,但是还是要根据自己公司的业务场景使用,废话不多说,下面先带大家了解一下什么是装饰器,然后再说一个小案例。

什么是装饰器decorator

装饰器(decorator)模式能够在不改变对象自身的基础上,动态的给某个对象添加额外的职责,不会影响原有接口的功能。如果你还是不理解上句话的意思,那我们举个例子:高阶函数,这个东西大家应该很熟悉,没错,你就把装饰器想象成高阶函数,当然啊,使用的时候千万不要跟高阶函数那样把函数当做返回值,或者是把函数当做参数传入,那样就废了😂,那样就真的成了高阶函数了,只是举个例子,一般装饰器的调用都是@函数名这种形式去调用的。

为什么要选择装饰者模式?

当我们编程过程中使用到一个方法的时候需要给这个方法添加新的行为但是我又不想改变有的函数结构,这时候就可以选择装饰器了,用装饰器修饰我们需要扩展的函数,这种模式就是装饰者模式,其实不单单装饰者模式可以实现,像ES5中的,高阶函数 也可以实现,但是这样写出来的代码会复杂一些,使用装饰者模式就显的很简洁,感兴趣的朋友可以去看一下node的框架Next.js的使用,这个框架虽然是Ts写的但是也是提供了很多的装饰器API。

  1. 文件目录
 decorators.js // 装饰器文件
 index.js   // 函数文件
 dist      // 打包之后的文件,会用node运行 

安装babel 点击这里去Babel官网

我们使用的ES5目前是不支持装饰器,装饰器被提出是在stage-3阶段,只能通过babel才能使用装饰器,所以我们需要安装babel

  1. 安装babel和babel核心库以及插件
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties 

这个时候你的package.json 是不下面的样子,你会发现我这边多一个core-js,下面会讲到为什么多

image.png 3. 创建babel.config.json

{
    "presets": [
      [
        "@babel/preset-env",
        {
          "targets": {
            "edge": "17",
            "firefox": "60",
            "chrome": "67",
            "safari": "11.1"
          },
          "useBuiltIns": "usage",
          "corejs": "3.6.5"
        }
      ]
    ],
     "plugins": [
      ["@babel/plugin-proposal-decorators", { "legacy": true }],
       "@babel/plugin-proposal-class-properties"
    ], 
  }

装饰器的属性

  • target为属性的类对象,target为类的时候key与descriptor都是undefined
  • key/name 为属性名字符串(比如说就是函数名称)
  • descriptor为属性key的属性描述符对象

简单的案例

需求:写一个http的请求类,使用装饰器来判断我们这个请求是否正确,并做错误收集

  • decorators.js
export function HttpDecorators(name){
         return function(target,n,discriptor){
                const funs = discriptor.value; 
                if(funs){
                    discriptor.value = function(...args){
                        // 处理请求
                        let params = name=='get'?[...args]:[JSON.stringify([...args])];
                         // 再次封装
                        return new Promise((resolve,reject)=>{
                            // 执行接口
                            funs.apply(this,params).then(res=>{  
                                // 判断状态
                                if(!res || res.code!='200'){
                                    // 错误请求进行收集
                                    cacheLog.push(res);
                                    resolve({text:'数据获取失败'}) 
                                }else{
                                    // 正确的直接格式化一下返回
                                    resolve({text:res.msg}) 
                                }
                              }).catch(err=>{ 
                                // 当存在参数错误的情况下返回错误并进行依赖收集
                                cacheLog.push(err);
                                reject(err)
                              });  
                        }) 
                    }
                }
                return discriptor
         }
}
  • index.js

import {HttpDecorators} from 'decorators.js'

class CreateHttpReqest{
      // 正确的请求
      @HttpDecorators('get') // 这里的参数是在定义的
      getFullData(methods){
          return new Promise((resolve,reject)=>{
            SuccessRequest(methods).then(res=>{
                    resolve(res);
                 }).catch(err=>{
                    reject(err);
                 })
          })
      }
      // 这个是个错误的请求
      @HttpDecorators('get')
      sendFulldata(methods){
          return new Promise((resolve,reject)=>{
            SuccessRequest(methods).then(res=>{
                    resolve(res);
                 }).catch(err=>{
                    reject(err);
                 })
          })
      }
}
const HttpReqest = new CreateHttpReqest();
// 成功请求
HttpReqest.getFullData('asd').then(res=>console.log(res));
// 错误请求
HttpReqest.sendFulldata().then(res=>console.log(res)).catch(err=>{
    console.log(err) 
});
// 错误请求
HttpReqest.sendFulldata().then(res=>console.log(res)).catch(err=>{
    console.log(err)
    console.log(cacheLog)
});

执行Bable编译生成文件

  • v执行之后会将我们的编译后的代码会输出到终端效果如下 npx babel .index.js

image.png

  • 编译并输出文件npx babel .index.js --out-file dist/index.js

问题:执行生成命令可能会报类似下面错,意思是没有core-js这个包,解决办法就是安装core-js,然后重新执行上面的编译命令即可

node:internal/modules/cjs/loader:959
throw err;
Error: Cannot find module 'core-js/modules/es.array.reduce.js'

image.png

  • 使用node运行dist/index.js文件
node dist/index.js
  • 效果

image.png

  • 完事!!!

最后

学习新技术或者新知识,不光是学完就完事了,学完你要学会怎样运用到项目中去,哪些场景适合它,并且使用它能为我们带来那些好处,这个才是学习的目的。如果你学习只是为了应付面试而不去实战,或者出于兴趣而学习,那我劝你利用这大好的时光不如自己多挣点钱。其实学习这个东西是一个很辛苦的事情但是只要你坚持了,并且挺过去了,那么后面的路就好走一些,如果你选择原地踏步,再想走的时候你会发现寸步难行。之所以现在的环境很卷,主要原因还是因为身边的人,当你发现身边的人挣的都比你多的时候,这个时候就应该思考一下,人家为什么挣的这么多,说明人家比你能吃苦,说白了人家就是:“够卷”,他们卷卷的不是自己而是他们身边的人,如果你不卷那被卷的人就是你了,这个社会就这样“弱肉强食”,生活就像一个永远不会生锈弹簧,你越弱,它越强,你越强,它越弱。