使用 es3、es5、es6 实现简单工厂模式、单例模式、观察者模式、发布订阅模式

前言

最近公司晋升岗位里面的一道小题《使用 es3 实现简单工厂模式、单例模式、观察者模式、发布订阅模式》,结束之后发现自己还要补好多´д` ;,抽空把里面几道小题,重新复习了一遍,扩充到es3、es5、es6,并做了笔记(再次膜拜公司出题大佬)。

完整版github代码地址:https://github.com/xuhuihui/smallDemo/blob/master/demo/demo3-设计模式/设计模式.md

待做的笔记:

  1. 实现简易模板字符串(6分)
    • 模拟实现es6模板字符串的${}即可;
    • 无需考虑特殊符号($、{、})的转义和嵌套等问题;
  2. 模拟实现简易版React-Redux

简单工厂模式

定义

定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。

适用范围

适用于复杂逻辑判断的情况,例如购物的商品,可以有上架中、出售中、下单中、出货中、派送中、到手等一系列复杂逻辑判断。

关于复杂逻辑的判断,有两种解决方案,策略模式和工厂模式。
1、策略模式vs工厂模式的区别
2、策略模式:JavaScript 复杂判断的更优雅写法

实现

es3代码

  var createPop = function (type, text) {
      // 创建一个对象,并对该对象做出扩展
      var o = new Object();
      o.content = text;
      o.show = function () {
        //  显示相同部分
        alert(type + ':' + this.content);
        switch (type) {
          case 'alert':
            // 警示框差异部分
            break;
          case 'confirm':
            // 确认框差异部分
            break;
          case 'prompt':
            // 提示框差异部分
            break;
        }
      };
      return o;
    };

es6代码

class Image {}
class Link {}
class Text {}

class ElementFactory {
    createElement(type, option){
         const ELEMENT = {
            "image" : Image,
            "text"  : Text,
            "link"  : Link
        }
        let ElementConstructor = ELEMENT(type),
            element = null;
        if(ElementConstructor){
            element = new ElementConstructor(option);
        }
        return element;
    }
}

单例模式

定义

  • 确保只有一个实例
  • 可以全局访问

适用范围

适用于弹框的实现, 全局缓存。
实现弹框的一种做法是先创建好弹框, 然后使之隐藏, 这样子的话会浪费部分不必要的 DOM 开销, 我们可以在需要弹框的时候再进行创建, 同时结合单例模式实现只有一个实例, 从而节省部分 DOM 开销。

实现

es3代码

简易版代码

1、优点:利用initialized属性,保持永远只创建一个EasySingletonIns实例,
2、缺点:不够封闭,可以从外部访问修改initialized属性。

    var EasySingletonIns = {
      initialized: false,
      // 实例扩展写在此处:
      belongTo: undefined,
      getBelongTo: function () {
        return EasySingletonIns.belongTo;
      }
    };
    var EasySingleton = {
      getInstance: function () {
        if (EasySingletonIns.initialized) {
          return EasySingletonIns;
        }
        EasySingletonIns.initialized = true;
        // 实例扩展也可写在此处:
        EasySingletonIns.belongTo = 'EasySingleton';
        return EasySingletonIns;
      }
    };

进阶版代码

1、优点:利用函数的闭包,阻止了内部属性被改变。
2、缺点:不好动态地传入想要的属性。

    var AdvancedSingleton = (function () {
      var ins = null;
      return function () {
        if (ins) {
          return ins;
        }
        if (!(this instanceof AdvancedSingleton)) {
          return new AdvancedSingleton();
        }
        function Inner() {
          // 实例扩展写在此处:
          this.belongTo = 'AdvancedSingleton @ constructor';         }
        Inner.prototype = this.constructor.prototype;
        // 原型扩展也可写在此处:
        ins = new Inner();
        return ins;
      };
    })();

酷炫版代码

1、优点:利用apply,可以给CoolSingletonCreator实例的prototype增加方法。
2、缺点:构造函数返回非自身实例的情况下,会出现问题。

    function CoolSingletonCreator(fn) {
      if (typeof fn !== 'function') {
        throw new Error('CoolSingletonCreator fn param must be function!');
      }
      var AdvancedSingletonForCool = (function () {
        var ins = null;
        return function () {
          if (ins) {
            return ins;
          }
          if (!(this instanceof AdvancedSingletonForCool)) {
            return new AdvancedSingletonForCool();
          }
          var args = arguments;
          function Inner() {
            fn.apply(this, args);
          }
          Inner.prototype = this.constructor.prototype;
          ins = new Inner();
          return ins;
        };
      })();
      AdvancedSingletonForCool.prototype = fn.prototype;
      AdvancedSingletonForCool.getInstance = function () {
        // 动态参数:(另外,针对只支持es3语法的浏览器的bind方法是没有的,需要自己实现,不在此处展开)
        var args = [null];
        return new (Function.prototype.bind.apply(AdvancedSingletonForCool, args.concat.apply(args, arguments)))();
      };
      return AdvancedSingletonForCool;
    }

es5代码

var Singleton = function(name) {
    this.name = name;
    //一个标记,用来判断是否已将创建了该类的实例
    this.instance = null;
}
// 提供了一个静态方法,用户可以直接在类上调用
Singleton.getInstance = function(name) {
    // 没有实例化的时候创建一个该类的实例
    if(!this.instance) {
        this.instance = new Singleton(name);
    }
    // 已经实例化了,返回第一次实例化对象的引用
    return this.instance;
}

es6代码

class Singleton {
    constructor(name) {
        this.name = name;
        this.instance = null;
    }
    // 构造一个广为人知的接口,供用户对该类进行实例化
    static getInstance(name) {
        if(!this.instance) {
            this.instance = new Singleton(name);
        }
        return this.instance;
    }
}

观察者模式

定义

定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

适用范围

1、当观察的数据对象发生变化时, 自动调用相应函数。比如 vue 的双向绑定;
2、每当调用对象里的某个方法时, 就会调用相应'访问'逻辑。比如给测试框架赋能的 spy 函数;

实现

es3代码

1、方法:使用prototype绑定函数实现。

    var Subject = (function () {
      // 观察者列表(不是必须的,可以由Subject自己处理)
      function ObserverList () {
        this.observerList = [];
      }
      ObserverList.prototype.add = function (observer) {
        return this.observerList.push(observer);
      };
      ObserverList.prototype.remove = function (observer) {
        this.observerList = this.observerList.filter(function (item) {return item !== observer;});
      };
      ObserverList.prototype.count = function () {
        return this.observerList.length;
      };
      ObserverList.prototype.get = function (index) {
        return this.observerList[index];
      };
      // 主题
      function Subject () {
        this.observers = new ObserverList();
      }
      Subject.prototype.addObserver = function (observer) {
        this.observers.add(observer);
      };
      Subject.prototype.removeObserver = function (observer) {
        this.observers.remove(observer);
      };
      Subject.prototype.notify = function () {
        var observerCount = this.observers.count();
        for (let i = 0; i < observerCount; i++) {
          var observer = this.observers.get(i);
          observer.update.apply(observer, arguments);
        }
      }
      return Subject;
    })();

es5代码

1、方法:使用 Object.defineProperty(obj, props, descriptor) 实现观察者模式。
2、缺点:Object.defineProperty() 不会监测到数组引用不变的操作(比如 push/pop 等);Object.defineProperty() 只能监测到对象的属性的改变, 即如果有深度嵌套的对象则需要再次给之绑定 Object.defineProperty();

 
    const data = {}
    const input = document.getElementById('input')
    Object.defineProperty(data, 'text', {
      set(value) {
        input.value = value
        this.value = value
      }
    })
    input.onchange = function(e) {
      data.text = e.target.value
    }

es6代码

1、方法:Proxy/Reflect 是 ES6 引入的新特性, 也可以使用其完成观察者模式。
2、优点:可以劫持数组的改变;defineProperty 是对属性的劫持, Proxy 是对对象的劫持;

var obj = {
  value: 0
}
var proxy = new Proxy(obj, {
  set: function(target, key, value, receiver) { 
    Reflect.set(target, key, value, receiver)
  }
})
proxy.value = 1 // 调用相应函数

发布订阅模式

定义

定义:在观察者模式中间,增加消息代理进行通信,来实现更更松的解耦。

发布订阅模式和观察者模式的差异:
1、在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
2、在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
3、观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
4、观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

适用范围

1、范围:MVC、MVVC的架构、Vue的源码实现和一些小游戏等。
2、优点: 在异步编程中实现更深的解耦。
3、缺点: 创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息以后,可能此消息最后都未发生,但是这个订阅者会始终存在于内存中。如果程序中大量使用发布-订阅的话,也会使得程序跟踪bug变得困难。

实现

es5代码

var Event = function() {
  this.obj = {}
}

Event.prototype.on = function(eventType, fn) { 
  if (!this.obj[eventType]) {
    this.obj[eventType] = []
  }
  this.obj[eventType].push(fn) // 推入数组
}

Event.prototype.emit = function() {
  var eventType = Array.prototype.shift.call(arguments)
  var arr = this.obj[eventType]
  for (let i = 0; i < arr.length; i++) { //推出调用函数
    arr[i].apply(arr[i], arguments)
  }
}

var ev = new Event()
ev.on('click', function(a) { // 订阅函数
  console.log(a) // 1
})
ev.emit('click', 1)          // 发布函数

es6代码

class Subject {
  constructor() {
    this.subs = []
    this.state = '张三' // 触发更新的状态
  }
  getState() {
    return this.state
  }
  setState(state) {
    if (this.state === state) {
      // 发布者一样
      return
    }
    this.state = state
    this.notify() // 有更新,触发通知
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  removeSub(sub) {
    const idx = this.subs.findIndex(i => i === sub)
    if (idx === -1) {
      // 不存在该观察者
      return
    }
    this.subs.splice(idx, 1)
  }
  notify() {
    this.subs.forEach(sub => {
      sub.update() // 与观察者原型方法update对应!
    })
  }
}

// 观察人,相当于订阅者
class Observer {
  update() {
    console.log('update')
  }
}

// 测试代码
const subject = new Subject()
const ob = new Observer()
const ob2 = new Observer()
ob2.update = function() {
  //修改update方法,实现不同逻辑
  console.log('laifeipeng')
}

//目标添加观察者了
subject.addSub(ob)
subject.addSub(ob2)

//目标发布消息调用观察者的更新方法了
// subject.notify(); // 不使用手动触发,通过内部状态的设置来触发
subject.setState('李四')

JavaScript 中常见设计模式

了解到的设计模式如下(゚o゚;;,以后每周有时间大概会补的吧(・_・;。

设计模式分为三种类型,共24种。

  • 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  • 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。




 

你可能感兴趣的:(前端)