浅谈Javascript设计模式之行为型模式

126 阅读3分钟

这是我参与 8 月更文挑战的第 11 天,活动详情查看: 8月更文挑战

前言

命令模式(Command Pattern)

用于将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及执行可撤销的操作。也就是说改模式旨在将函数的调用、请求和操作封装成一个单一的对象。

function Command(execute, undo, value) {
    this.execute = execute;
    this.undo = undo;
    this.value = value;
}
var AddCommand = function (value) {
    return new Command(add, sub, value);
};
var SubCommand = function (value) {
    return new Command(sub, add, value);
};
var MulCommand = function (value) {
    return new Command(mul, div, value);
};
var DivCommand = function (value) {
    return new Command(div, mul, value);
};

命令具有以下的优点:

  (1)命令模式使新的命令很容易地被加入到系统里。

  (2)允许接收请求的一方决定是否要否决请求。

  (3)能较容易地设计一个命令队列。

  (4)可以容易地实现对请求的撤销和恢复。

  (5)在需要的情况下,可以较容易地将命令记入日志。

解释器模式(Interpreter Pattern)

解释器模式用于构造一个简单的语言解释器,将字符串按照自定义的方式解释执行 ,是一种不常用的设计模式。除非从事底层开发自己需要去定义较为复杂的表达式,否则基本上不同这个设计模式,而且像很多语言其实都有提供动态代码的执行或者VM的功能。

比如实现一个对字符串判断是否是偶数

function Interpreter(str) {
    return Number(str) % 2 !== 0;
}

优点:

  • 易于扩展和修改文法规则。增加时只需要增加新的终结符表达式,符合开关原则。

缺点:

  • 对于复杂文法难以维护,会充满非终结表达式。
  • 执行效率低,由于使用了大量循环和递归调用,在解释复杂句子时速度很慢。

迭代器模式(Iterator Pattern)

基本上是每种语言都会实现的一种模式,提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

function each(arr, fn) {
    for (let item of arr) {
        fn(item)
    }
}
each([1, 2, 3], function(item) {
    console.log(item)
})

中介者模式(Mediator Pattern)

中介者模式主要用于一个系统中存在大量的对象,而且这些大量的对象需要互相通信,因为两个对象需要通信,一个对象必须要持有另一个对象,这样就会导致,系统里,每个对象都互相引用,会引起混乱,中介者把所有的对象都统一管理起来,其他的对象通过中介者去和别的对象通信。

我们可以大致看下Vue的实现

  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this

    /*如果是数组的时候,则递归$on,为每一个成员都绑定上方法*/
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      /*这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      /*将类数组的对象转换成数组*/
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      /*遍历执行*/
      for (let i = 0, l = cbs.length; i < l; i++) {
        cbs[i].apply(vm, args)
      }
    }
    return vm
  }

备忘录模式(Memento Pattern)

备忘录设计模式非常的适合在缓存还原的场景,就是我把某个状态数据先做缓存,存于内存或者其他的媒介中,在切换回来此状态时直接到缓存中的状态数据给导出,不需要再一步步的进行new操作,提高对象实体生成的效率,提高工作效率和场景体验。

比如,实现一个斐波那契数列的求和

function fn(n) {
    if (n < 2) {
        return n;
    }
    return fn(n - 1) + fn(n - 2)
}

当我们把n稍调大的时候,就可以发现速度特别慢,甚至会爆栈。原因是中间存在了大量的重复计算,我们来通过备忘录模式做一波优化。

const memento = {};
function fn(n) {
    if (n < 2) {
        return n;
    }
    if (memento[n]) {
        return memento[n]
    }
    memento[n] = fn(n - 1) + fn(n - 2)
    return memento[n]
}

最后打波小广告,美团校招社招内推,不限部门,不限岗位,不限投递数量,海量hc,快来快来~