修饰器模式

709 阅读3分钟

修饰器模式

什么是修饰器模式? 修饰器是es7引入的,用通俗易懂的话解释就是:执行一个方法,在代码编译时执行,把修饰器下满的class或者属性作为参数传入,并返回一个新的方法,新的方法再在运行时执行。

上栗子:

@addStaticProp
class Test {
}

function addStaticProp(target) {
    target._name  = '新增的属性';
}

console.log(Test._name); // 此处会输出 '新增的属性'

@addStaticProp的作用是直接修改了它下方的class的属性,添加了一个新的静态属性,就类似一个加工厂,把需要的加工的物品丢进去,而我们不用管这个物品内部有什么,我们只是在外部给该物品添加多一些内容。此时再回顾上面说的那句话就很容易理解了。

其实在其他语言早已经有类似的方法了,例如Java的注解,就是跟我们的修饰器一毛一样的。

class使用修饰器:

自由添加属性和值:

function addCustomProp (key, value) {
    return function (target) {
        target[key] = value
    }
}

@addCustomProp('isShow', true)
class Test {
}
@addCustomProp('name', 'yixin')
class Test {
}

console.log(Test.isShow); // true
console.log(Test.name); // yixin

给类自由添加实例方法:

    function mixins(...list) {
        return function (target) {
            Object.assign(target.prototype, ...list)
        }
    }
    
    const say = () => {
        console.log('hello world!')
    }
    
    @minxins(say)
    class TestClass {}
    
    let p1 = new TestClass();
    p1.say(); // hello world

此处的修饰器直接给class的原型上面添加了方法,所以实例出来的对象都会有该方法。

方法使用修饰器

修饰器作用在方法上面会接受到三个参数,参数1:要修饰的目标对象,参数2:要修饰的属性名,参数3:该属性的描述对象。 描述对象内容如下:

  1. configurable是否可删除
  2. enumerable是否可枚举(遍历)
  3. value属性值
  4. writable是否可修改

来个下酒菜,把实例属性修改为只读:

function readonly(target, name, descriptor) {
    descriptor.writable = false
    return descriptor;
}
class Test{
    @readonly
    public name = 'yixin';
}

let p1 = new Test();
p1.name = '改名了';
console.log(p1.name); // yixin 修改失败

再来个最常用的日记修饰器log

function log(target, name, descriptor) {
    let oFn = descriptor.value;
    descriptor.value = function() {
        console.log(`方法${name}正在执行,参数为:`, arguments);
        return oFn.apply(null, arguments)
    }
    return descriptor;
}

class Test{
    @log
    public add(a, b) {
        return a + b
    }
}
(new Test()).add(1,3); // 方法$add正在执行,参数为:`, [1,3]

多个修饰器使用:

方法可以迭代使用多个修饰器,不过由于修饰器是编译时执行,运行时执行修饰器编译后的代码,所以当一个方法有多个修饰器的时候,那么该方法会从外到内进入修饰器,然后由内向外执行。

    function consoleLog(value) {
        return function (target, name, descriptor) {
            console.log(value);
        }
    }
    
    class Test {
        @consoleLog(1);
        @consoleLog(2);
        say() {}
    }
    
    (new Test()).say(); // 即使最上面的修饰器先编译,但是结果依旧是先输出2,再输出1。

防抖修饰器

防抖是我们开发中使用最多的方法

function debounce(wait) {
  return function(target, name, descriptor) {
    const oFn = descriptor.value
    let timer = null
    descriptor.value = function() {
      clearTimeout(timer);
      timer = setTimeout(() => {
        oFn.apply(_this, arguments)
      }, wait)
    }
  }
}

class Test {
    @debounce(500)
    search() {
        console.log('查询操作');
    }
}
let p1 = new Test();
p1.search();
p1.search();
p1.search();
p1.search();

节流修饰器

节流也是我们开发中使用最多的方法

    function throttle(wait) {
      return function(target, name, descriptor) {
        const oFn = descriptor.value
        let timer = null
        descriptor.value = function() {
          if (timer) return
          fn.apply(_this, arguments)
          timer = setTimeout(() => {
            canRun = null
          }, wait)
        }
      }
    }
    
    class Test {
        @throttle(500)
        search() {
            console.log('查询操作');
        }
    }
    let p1 = new Test();
    p1.search();
    p1.search();
    p1.search();
    p1.search();