Skip to content

装饰器模式

环境

浏览器和 Node 目前都不支持装饰器语法,需要大家安装 Babel 进行转码

shell
npm i -D babel-preset-env babel-plugin-transform-decorators-legacy

编写 .babelrc 文件

json
{
	"presets": ["env"],
	"plugins": ["transform-decorators-legacy"]
}

本地运行

shell
npm i babel-cli -D
json
{
    "scripts": {
        "start": "babel-node xxx.js" 
    }
}

概念

在不改变原对象的基础上,通过对其进行包装拓展,使原有对象可以满足用户的更复杂需求

OOP -- 面向对象编程模式

给对象添加功能时,通常会采用继承的方式,但继承的方式也有许多问题

  1. 父类和子类存在强耦合关系,父类改变时,子类同时也需要改变。同时子类需要知道父类的接口等

  2. js 只能单继承,当需要对多个对象添加功能时,特别麻烦

AOP -- 面向切面编程

把功能逻辑抽离出来,在通过动态切入的方式注入到业务逻辑中,是可以保证业务内部的高内聚和业务模块之间的低耦合。

装饰和 AOP 本质是一样的

日志上报例子

业务想知道一个业务方法,执行前的时间,与执行后的时间。但这个业务方法不是你写的,所以你并不想去动别人的代码。这个时候就可以用 java spring 实现的前置通知和后置通知的思路

ES5

js
// 前置通知
Function.prototype.before = function (beforefn) {
  const __self = this
  return function () {
    beforefn.apply(this, arguments)
    return __self.apply(this, arguments)
  }
}
js
// 后置通知
Function.prototype.after = function (afterfn) {
  const __self = this
  return function () {
    const ret = __self.apply(this, arguments)
    afterfn.apply(this, arguments)
    return ret
  }
}

这样就可以很快的完成新的业务功能了

js
function Person(name) {
  this.name = name
}
Person.prototype.say = function () {
  console.log(`my name is ${this.name}`)
}

// 通知函数
function log() {
  console.log(`person say on ${new Date()}`)
}

Person.prototype.say = Person.prototype.say.before(log).after(log)

const musi = new Person('musi')
musi.say()
// person say on Sun Nov 06 2022 21:32:32 GMT+0800 (中国标准时间)
// my name is musi
// person say on Sun Nov 06 2022 21:32:32 GMT+0800 (中国标准时间)

ES7

js
function before(beforefn) {
    return function (target, name, descriptor) {
        const value = descriptor.value
        descriptor.value = function (...arg) {
            beforefn.apply(this, arg)
            return value.apply(this, arg)
        }
        return descriptor
    }
}

function after(afterfn) {
    return function (target, name, descriptor) {
        const value = descriptor.value
        descriptor.value = function (...arg) {
            const ret = value.apply(this, arg)
            afterfn.apply(this, arg)
            return ret
        }
        return descriptor
    }
}
js
// 通知函数
function log() {
  console.log(`person say on ${new Date()}`)
}

class Person {
    constructor(name) {
        this.name = name
    }
    
    @before(log)
    @after(log)
    say() {
        console.log(`my name is ${this.name}`)
    }
}

const musi = new Person('musi')
musi.say()

装饰器使用

类装饰器

js
function classDecorator(target) {
    target.hasDecorator = true
    return target
}

// 将装饰器“安装”到Button类上
@classDecorator
class A {
    // Button类的相关逻辑
}

方法装饰器

js
function funcDecorator(target, name, descriptor) {
    const originValue = descriptor.value
    descriptor.value = function() {
        // ...
        return originValue.apply(this, arguments)
    }
    return descriptor
}

class A {
    @funcDecorator
    fn() {
        
    }
}

带参数的方法装饰器

js
function funcDecorator(...arg) {
    return function (target, name, descriptor) {
        // 用 arg 做些什么
        const originValue = descriptor.value
        descriptor.value = function () {
            // ...
        	return originValue.apply(this, arguments)
        }
        return descriptor
    }
}

descriptor 和 Object**.**defineProperty(obj, prop, descriptor) 里的是一个东西。专门用来描述对象的属性