学习JavaScript 设计模式 - 装饰器模式

130 阅读1分钟

向一个对象添加新的功能, 同时又不改变其结构

创建一个装饰类, 用来包装原有的类, 提供额外的功能, 相比生成子类来说更为灵活

应用场景

新入职的同事接手一个老项目, 对业务不熟, 并且老项目封装好的方法不让动(改), 这时候我们就可以拓展一个方法,并且不影响封装好的函数下完成功能

例子

比如项目封装了一个上传文件的函数, 现在新增需求 “每次上传前判断下用户角色”

    import axios from 'axios'
    
    class Upload {
        constructor(url, file) {
            this.url = url
            this.file = file
        }
        http() {
            axios.post(this.url, this.file)
        }
    }

上面这个是老项目封装好的方法, 接下来我们拓展下

    class Decorator {
        constructor(upload, user) {
            this.upload = upload
            this.user = user
        }
        http() {
            //新增判断用户权限
            if (this.user.code < 1) {
                alert("您没权限上传文件")
            } else {
                this.upload.http()
            }
        }
    }

接下来就是调用了

    const upload = new Upload('http://www.xx.com', '1.png')
    
    //修饰后的上传方法
    const decorateUpload = new Decorator(upload, {code: 3})
    decorateUpload.http() //正常发送请求

这时候换了个用户code 为 -1

    const otherUser = new Decorator(upload, {code: -1})
    decorateUpload.http()   
    //此时请求就被拒绝了, 提示无权限
ES7 装饰器
    //修饰器函数 (第一个参数,就是所要修饰的目标类)

    function myLog(target) {
       target.log = function (msg) {
          console.log(msg)
       }
    }
    
    
    @myLog class Test {
        ...
    }
    
    Test.log('我是一个小日志') //这里就正常打印出msg 
    

修饰器不但可以修饰类,还可以修饰类的属性

function readOnly(target, key, descriptor) {
    console.log(target, key, descriptor)
    
    descriptor.writable = false;   //其实就是Object.defineProperty(target, key, {writable: false })
    return descriptor;
}

class Person {
    @readOnly static name= '装饰器模式'

    @readOnly like() {
        console.log('我是不可修改的方法')
    }
}

接下来尝试下修改类属性

    console.log(Person.name)   //装饰器模式
    Person.name = '变变变'   //TypeError: Cannot assign to read only property 'name' of ...

图1:

name只读.png

    const p = new Person()
    p.like = {}  //TypeError: Cannot assign to read only property 'like' of object '#<Person>'

图2:

like方法只读.png

可以看到两个只读起作用了, 并且还可以同时用多个修饰器

    function readOnly() {...}
    function log() {...}
    
    class Person {
        @log
        @readOnly 
        like() {
            console.log('我是不可修改的方法, 并且会打印日志')
        }
    }

划重点: 当存在多个修饰器时, 就跟洋葱模型一样, 先从外到内进入 再从 内到外执行

看下面这个例子输出顺序就知道了

function add(num) {
    console.log('进入装饰器', num)
    return target => {
        console.log('执行装饰器', target, num++)
    }
}

class Person {
    @add(1)
    @add(2)
    age = 10
}

new Person()

图3:

0be4f941fd45e34db5e951c6f145cfe0.png

但是这个es7修饰器不能用于普通函数,只能修饰 , 日常开发也不会全部自己写这么多修饰方法, core-decorators.js 它提供了几个常见的修饰器

最后的轻语

在书里看到一句话,不是很理解:

在现实世界中思考理论问题, 在理论世界中思考现实问题