WEB前端常用的设计模式总结

创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。包括简单工厂模式、工厂方法模式、抽象工厂模式、单例模式、生成器模式、原型模式 5 种。

结构型模式:用于描述如何将类或对象按某种布局组成更大的结构。包括代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式、组合模式  7 种。

行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。模板方法、策略模式、命令模式、职责链模式、状态模式、观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式、解释器模式 11种。 

【工厂模式】:

  • 工厂模式是创建对象常见的设计模式,又叫做静态工厂模式,它把创建对象的具体逻辑进行封装,由一个工厂对象决定创建某一个类的实例。
  1. 调用者创建对象时只要知道其名称即可
  2. 扩展性高,如果要新增一个产品,直接扩展一个工厂类即可。
  3. 隐藏产品的具体实现,只关心产品的接口。
  • 应用场景:需要依赖具体环境创建不同的实例,这些实例都有相同的行为,这时候我们可以使用工厂模式,简化实现的过程,同时也可以减少每种对象所需的代码量,有利于消除对象间的耦合,提供更大的灵活性;将new操作简单封装,遇到new的时候就应该考虑是否用工厂模式;
<script>
    // 定义一个Person类
    function Person(name){
      this.name = name
    }
    Person.prototype.getName = function(){
      console.log(this.name);
    }
    // 定义一个Car类
    function Car(model){
      this.model = model
    }
    Car.prototype.getModel = function(){
      console.log(this.model);
    }
    // 构造
    function create(type,param){
      return new this[type](param)
    }
    create.prototype = {
      person:Person,
      car:Car
    }
    // 实例化
    var person1 = new Create('person','zhangsan')
    person1.getName()

  </script>

【抽象工厂模式】:

  • 希望在不改变原对象的基础上,通过对其拓展功能和属性来实现更复杂的逻辑。
  • 抽象工厂模式包含如下 4 种角色:抽象工厂、具体工厂、抽象产品、具体产品。

  • 使用场景:继承同一父类、实现同一接口的子类对象,由给定的多个类型参数创建具体的对象。

如汽车厂商不但能生产 Car,也可以生产发动机 Engine,这就是两个产品等级结构,一个产品族。再下面例子中,如果我们需要新增一个Audi 厂商,我们只需要新增一个具体的AudiFactory工厂和一个具体产品的AudiCar 和 AudiEngine类,然后进行实例化,便可以实现。

 <script>
    // 定义生产汽车的抽象工厂
    class makeCarFactory {
      //生产汽车
      createCar() {
        throw new Error('不能调用抽象方法,请自己实现');
      }
      //生产汽车发动机
      createEngine() {
        throw new Error('不能调用抽象方法,请自己实现');
      }
    }
    // 定义具体生产工厂,实现抽象工厂
    class BenzFactory extends makeCarFactory {
      createCar() {
        return new BenzCar();
      }
      createEngine() {
        return new BenzEngine();
      }
    }

    // 定义抽象产品类Car
    class Car {
      dirve() {
        throw new Error('不能调用抽象方法')
      }
    }
    // 定义具体产品类BenzCar
    class BenzCar extends Car {
      dirve () {
        console.log('Benz drive');
      }
    }

    // 定义抽象产品Engine
    class Engine {
      start() {
        throw new Error('不能调用抽象方法')
      }
    }
    // 定义具体产品类BenzEngine
    class BenzEngine extends Engine {
      start() {
        console.log('Benz engine start');
      }
    }

    // 实例化
    let benz = new BenzFactory();
    let benzCar = benz.createCar();
    let benzEngine = benz.createEngine();
    benzCar.dirve(); //Benz drive
    benzEngine.start();  //Benz engine start
  </script>

【建造者模式】:

  • 建造者模式(Builder)是一种比较复杂使用频率较低的创建型设计模式,也叫生成器模式,主要用于将一个复杂对象的构建与他的表现分离,使得同样的构建过程可以创建不同的表示。
  • 例如生产汽车,分步骤创建安装不同的零件。如果创建逻辑简单则没有拆分的必要。
  • 使用场景:需要将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示的意图;创建时有很多必填参数需要验证;创建时参数求值有先后顺序、相互依赖;创建有很多步骤,全部成功才能创建对象
<script>
class Programmer {
  age: number
  username: string

  constructor(p) {
    this.age = p.age
    this.username = p.username
  }

  toString() {
    console.log(this)
  }
}

class Builder {
  age: number
  username: string

  build() {
    if (this.age && this.username && this.color) {
      return new Programmer(this)
    } else {
      throw new Error('缺少信息')
    }
  }

  setAge(age: number) {
    if (age > 18 && age < 36) {
      this.age = age
      return this
    } else {
      throw new Error('年龄不合适')
    }
  }

  setUsername(username: string) {
    if (username !== '小明') {
      this.username = username
      return this
    } else {
      throw new Error('小明不合适')
    }
  }
}

const p = new Builder()
  .setAge(20)
  .setUsername('小红')
  .build()
  .toString()
  </script>

【单例模式】:

  • 我们无论创建多少次对象都指向同一个
  • 使用场景:线程池、全局缓存、浏览器中的window对象。
  • 例子:当点击页面的登录案例,会弹出登录弹窗,这个弹窗无论点击多少次只能被创建一次,那么这个弹窗就可以用单例模式创建。
<body>
  <button id="btn">点击</button>
  <div style=""></div>
  <img src="" alt="">
  <script>
    // 单例构造函数
    function Person(name) {
      this.name = name
    }
    // 获取实例的名字
    Person.prototype.getName = function () {
      console.log(this.name);
    }
   //创建实例
    var CreateSinglePerson = (function () {
      // 定义instance,用来表示person实例是否已经创建
      var instance;
      return function (name) {
        if (!instance) {
          instance = new Person(name)
        }
        return instance
      }
    })()
    var a = new CreateSinglePerson('a')
    var b = new CreateSinglePerson('b')
    console.log(a === b);  //true  说明每次实例的对象都是同一个

    // -----------------------------------------------
    // 遮罩层和弹出框
    var singleton = function (fn) {
      var instance;
      return function () {
        return instance || (instance = fn.apply(this, arguments))
      }
    }
    // 创建遮罩层
    var Mask = function () {
      // 创建div元素
      var mask = document.createElement('div')
      // 设置样式
      mask.style.position = 'fixed'
      mask.style.top = '0'
      mask.style.right = '0'
      mask.style.bottom = '0'
      mask.style.left = '0'
      mask.style.opacity = '0.75'
      mask.style.backgroundColor = '#000'
      mask.style.display = 'none'
      mask.style.zIndex = '999'
      document.body.appendChild(mask)
      // 单击隐藏遮罩层
      mask.onclick = function () {
        this.style.display = 'none'
      }
      return mask
    }
    // 创建登录弹窗
    var Login = function () {
      var login = document.createElement('div')
      login.style.position = 'fixed'
      login.style.left = '50%'
      login.style.top = '50%'
      login.style.zIndex = '1000'
      login.style.display = 'none'
      login.style.padding = '50px 80px'
      login.style.backgroundColor = '#fff'
      login.style.border = '1px solid #ccc'
      login.style.borderRadius = '6px'
      login.innerHTML = '登录'
      document.body.appendChild(login)
      return login
    }
    document.getElementById('btn').onclick = function () {
      var Omask = singleton(Mask)()
      Omask.style.display = 'block'
      var Ologin = singleton(Login)()
      Ologin.style.display = 'block'
      var w = parseInt(Ologin.clientWight)
      var h = parseInt(Ologin.clientHight)
    }
  </script>
</body>

【原型模式】:

  • 原型模式解决了方法或者属性不能共有的问题,在原型模式中,把实例之间相同的属性和方法提取成共有的属性和方法,即:想让谁共有,就把它放在 类.prototype上。
  • 使用场景:原型模式是基于已有的对象克隆数据,而不是修改原型链;创建对象的代价太大,而同类的不同实例对象属性值基本一致,通过原型克隆的方式可以节约资源;不可变对象可以用浅克隆实现,可变对象用深克隆实现,深克隆占用资源多;同一对象不同时间版本,可以对比没变化的浅克隆,变化的深克隆,然后新版本替换旧版本。
<script>
    function CreatePerson(name,age) {
      this.name = name,
      this.age = age;
    }

    CreatePerson.prototype.wirte = function() {
      console.log(this.name + 'write');
    }

    var p1 = new CreatePerson('wang', 18);
    var p2 = new CreatePerson('ling', 15);
    p1.wirte(); 
</script>

 【装饰器模式】:

  • 希望在不改变原对象的基础上,通过对其拓展功能和属性来实现更复杂的逻辑。
  • 使用场景:装饰器类是对原始功能的增强;装饰器类和原始类继承同样的父类,这样我们可以对原始类嵌套多个装饰器类;主要解决继承关系过于复杂的问题,通过组合来替代继承;可以通过对原始类嵌套使用多个装饰器。
<script>
    //生产手机
    class Cellphone {
        create() {
            console.log('生成手机')
        }
    }

    // 装饰器装饰生产手机的同时生产手机壳
    class Decorator {
        constructor(cellphone) {
            this.cellphone = cellphone
        }
        create() {
            // 生产手机
            this.cellphone.create()
           // 生产手机壳
            this.createShell(cellphone)
        }
        createShell() {
            console.log('生成该类型手机的手机壳')
        }
    }
    
    let cellphone = new Cellphone()
    cellphone.create() // 只能生产手机

    let newPhone = new Decorator(cellphone)
    newPhone.create() // 生产该手机的手机壳
</script>

【组合模式】:

  • 组合模式作用于将多个部分通过组合变成一个整体
  • 通过对象的多态表现,使得用户对单个对象和组合对象的使用具有一致性。
  • 使用场景:将一组对象组织成树形结构,以表示一种 “部分-整体 ”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性(递归遍历)。

例1、

<script>
    // 单个指令
    var closeDoorCommand = {
      excute:function(){
        console.log('关门');
      }
      // excute() {
      //  console.log('关门');
      // }
    }
    var openPcCommand = {
      excute: function () {
        console.log('打开电脑');
      }
    }
    var openQQCommand = {
      excute: function () {
        console.log('登录QQ');
      }
    }
    // 调用者
    var MacroCommand = function () {
      return {
        commandsList: [],
        // 把每一个指令放进去
        add: function (command) {
          this.commandsList.push(command)
        },
        // 遍历执行
        excute: function () {
          for (var i = 0, command; command = this.commandsList[i++];) {
            command.excute()
          }
        }
      }
    }
    var macroCommand = MacroCommand()
    macroCommand.add(closeDoorCommand)
    macroCommand.add(openPcCommand)
    macroCommand.add(openQQCommand)
    macroCommand.excute()
  </script>

例2、

<script>
window.onload = function(){
  //组合寄生式继承
  function inheritPrototype(subClass,superClass){
    function F(){};
    F.prototype = superClass.prototype
    subClass.prototype = new F()
    subClass.prototype.constructor = subClass
  }
  // 容器基类
  function Container(){
    this.children = []
    this.element = null
  }
  Container.prototype = {
    init:function(){
      throw new Error('请重写init方法')
    },
    add:function(child){
      this.children.push(child)
      this.element.appendChild(child.element)
      return this
    }
  }

  // 基于容器基类创建表单容器
  function CreateFrom(id,method,action,parent){
    Container.call(this)
    this.id = id || ''
    this.method = method || 'get'
    this.action = action || ''
    this.parent = parent 
    this.init()
  }
  inheritPrototype(CreateFrom,Container)
  CreateFrom.prototype.init = function(){
    this.element = document.createElement('from')
    this.element.id = this.id
    this.element.method = this.method
    this.element.action = this.action
  }
  CreateFrom.prototype.show = function(){
    this.parent.appendChild(this.element)
  }

  // 创建行容器组件
  function CreateLine(className){
    Container.call(this)
    this.className = className === undefined? 'form-line':'form-line'+className
    this.init()
  }
  inheritPrototype(CreateLine,Container)
  CreateLine.prototype.init = function(){
    this.element = document.createElement('div')
    this.element.className = this.className
  }

  // 创建label组件
  function CreateLabel(text,forName){
    this.text = text || ''
    this.forName = forName || ''
    this.init()
  }
  CreateLabel.prototype.init = function(){
    this.element = document.createElement('label')
    this.element.setAttribute('for',this.forName)
    this.element.innerHTML = this.text
  }

  // 创建input
  function CreateInput(type,id,name,defaultValue){
    this.type = type || ''
    this.id = id || ''
    this.name = name || ''
    this.defaultValue = defaultValue || ''
    this.init()
  }
  CreateInput.prototype.init = function(){
    this.element = document.createElement('input')
    this.element.type = this.type
    this.element.id = this.id
    this.element.name = this.name
    this.element.value = this.defaultValue
  }

  var form = new CreateFrom('owner-from','GET','./a.html',document.body)
  var userLine = new CreateLine()
                  .add(new CreateLabel('用户名','user'))
                  .add(new CreateInput('text','user','user'))
  var pwdLine = new CreateLine()
                  .add(new CreateLabel('密码','pwd'))
                  .add(new CreateInput('password','pwd','pwd'))
  var submitLine = new CreateLine()
                  .add(new CreateInput('submit','','','登录'))
  form.add(userLine).add(pwdLine).add(submitLine).show()
}
  </script>

【代理模式】:

  • 所谓的的代理模式就是为一个对象找一个替代对象,以便对原对象进行访问。使用代理的原因是我们不愿意或者不想对原对象进行直接操作,我们使用代理就是让它帮原对象进行一系列的操作,等这些东西做完后告诉原对象就行了。

  • 使用场景:给原类添加非功能性需求,为了将代码与原业务解耦;业务系统的非功能性需求开发:监控、统计、鉴权、限流、日志、缓存。

<script>
    var Flower = function () {}
    // 小明
    var xiaoming = {
      sendFlower: function (target) {
        // 拿到花
        var flower = new Flower()
        // 目标对象收花
        target.receiveFlower(flower)
      }
    }
    // 代理对象
    var friend = {
      receiveFlower: function (flower) {
        // 接受小明给的花,并给女神
        nvshen.receiveFlower(flower)
      }
    }
    // 女神
    var nvshen = {
      receiveFlower: function (flower) {
        console.log("我收到了来自小明的花");
      }
    }
    xiaoming.sendFlower(friend)
</script>

【外观模式】:

  • 为子系统的一组接口提供一个一致的界面,定义了一个高层接口,这个接口使子系统更加容易使用。
  • 外观模式很常见,它其实就是通过一个单独的函数,来简化对一个或多个更大型,更为复杂的函数的访问,是一种对复杂操作的封装,通过把复杂操作封装,调用时直接用方法调用。
  • 使用场景:将多个后端接口请求合并为一个,提高响应速度,解决性能问题;通过封装细粒度接口,提供组合各个细粒度接口的高层次接口,来提高接口的易用性。像Ajax封装。
<script>
    // 就是系统中复杂的逻辑进行抽象与封装,提供一个更简单易用的API
    // 像JQuery中把原生DOM操作进行抽象与封装,并消除浏览器之间的兼容问题

    // DOM事件的绑定与取消
    function addEvent(element, event, handler) {
      if (element.addEventLister) {
        element.addEventLister(event, handler, false)
      } else if (element.attachEvent) {
        element.attachEvent('on' + event, handler)
      } else {
        element['on' + event] = fn
      }
    }

    function removeEvent(element, event, handler) {
      if (element.addEventLister) {
        element.removeEventLister(event, handler, false)
      } else if (element.attachEvent) {
        element.removeEvent('on' + event, handler)
      } else {
        element['on' + ecent] = null
      }
    }
</script>

【桥接模式】:

  • 桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。
  • 一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,一个类存在两个独立变化的维度,且这两个维度都需要进行扩展,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使他们在抽象层建立一个关联关系。比如,那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统。
  • 使用场景:将抽象和实现解耦,让它们可以独立变化;一个类存在多个独立变化的维度,我们通过组合的方式,让多个维度可以独立进行扩展;非常类似于组合优于继承原则。
<script>
    class Color {
      constructor(name) {
        this.name = name;
      }
    }
    class Shape {
      constructor(name, color) {
        this.name = name,
        this.color = color;
      }
      draw() {
        console.log(this.color + '的' + this.name);
      }
    }
    let red = new Color('red');
    let circle = new Shape('circle', red)
    circle.draw()
</script>

【享元模式】:

  • 运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,
  • 在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
  • 使用场景:一个程序中使用了大量的相似对象;由于使用了大量对象,造成很大的内存开销;对象的大多数状态都可以变为外部状态;剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
  • 享元模式要求将对象的属性划分为内部状态和外部状态(状态在这里通常指属性),其目标是尽量减少共享对象的数量。

有个服装厂,生产了男女服装各50种款式,为了推销需要找模特来拍照,正常可能会找男女模特各50个,每个模特分别穿一种服装拍一组照片。但是如果这种,后面款式越来越多,也就意味着模特也越来越多,仔细分析一下,其实不管有多少种类衣服,我们只需要男女各一个模特来穿衣服进行拍照也可实现该需求。

<script>
    // 构建享元对象
    class Model {
      constructor(id, gender) {
        this.gender = gender,
        this.name = `王${gender}${id}`;
      }
    }
    // 构建享元工厂
    class ModelFactory {
      // 单例模式
      static create(id, gender) {
        if(this[gender]) {
          return this[gender];
        }
        return this[gender] = new Model(id, gender);
      }
    }
    // 管理外部状态
    class TakeClothesManger {
      // 添加衣服款式
      static addClothes(id, gender, clothes) {
        const model = ModelFactory.create(id, gender);
        this[id] = {
          clothes,
          model
        }
      }
      // 拍照
      static takePhoto(id) {
        const obj = this[id];
        console.log(`${obj.model}模特${obj.model.name}穿${obj.clothes}拍了张照`);
      }
    }
    //实例化
    for(let i=0; i< 50; i++) {
      TakeClothesManger.addClothes(i, '男', `衣服${i}`);
      TakeClothesManger.takePhoto(i);
    }
    for(let i=0; i < 100; i++) {
      const {addClothes, takePhoto} = TakeClothesManger;
      TakeClothesManger.addClothes(i, '女', `衣服${i}`);
      TakeClothesManger.takePhoto(i);
    }
</script>

【观察者模式】:

  • 观察者模式又叫发布订阅模式消息模式。这种模式一般会定义一个主体和众多个的个体,这里的主体可以想象成一个消息中心,里面有各种各样的消息,而众多的个体可以订阅里面不同的消息,当未来消息中心发布某个消息的时候,订阅过他的个体就会得到通知。
  • 使用场景:将观察者与被观察者解耦;发布订阅模式有发布订阅调度中心(中间商),观察者模式没有。

例1、

<script>
    var msgCenter = (function(){
      // 存储消息
      var _msg = {}
      return {
        // 订阅消息
        register:function(type,fn){
          if(_msg[type]){
            _msg[type].push(fn)
          }else{
            _msg[type] = [fn]
          }
        },
        // 发布消息
        fire:function(type,args){
          if(!_msg[type]){
            return
          }
          var params = {
            type:type,
            args:args || {}
          }
          for(var i=0;i < _msg[type].length;i++){
            _msg[type][i](params)
          }
        },
        // 取消消息
        cancle:function(type,fn){
          if(!_msg[type]){
            return
          }
          for(var i =0;i < _msg[type].length;i++){
            if(_msg[type][i] === fn){
              _msg[type].splice(i,1)
              break
            }
          }
        }
      }
    })()

    function Person(){
      this.alreadyRegister = {}
    }
    Person.prototype.register = function(type,fn){
      if(this.alreadyRegister[type]){
        console.log('你已经订阅过这个消息了,请不要重复订阅');
      }else{
        msgCenter.register(type,fn)
      // 本地存储一份
      this.alreadyRegister[type] = fn
      }
    }
    Person.prototype.cancle = function(type){
      msgCenter.cancle(type,this.alreadyRegister[type])
      delete this.alreadyRegister[type]
    }

    var person1 = new Person()
    var person2 = new Person()
    var person3 = new Person()
    var person4 = new Person()

    person1.register('carInfo',function(e){
      console.log('person1订阅了' + e.type +'的消息,内容是' +e.args.info);
    })
    person1.register('catInfo',function(e){
      console.log('person1订阅了' + e.type +'的消息,内容是' +e.args.info);
    })
    person2.register('carInfo',function(e){
      console.log('person2订阅了' + e.type +'的消息,内容是' +e.args.info);
    })
    person3.register('carInfo',function(e){
      console.log('person3订阅了' + e.type +'的消息,内容是' +e.args.info);
    })
    person4.register('foodInfo',function(e){
      console.log('person3订阅了' + e.type +'的消息,内容是' +e.args.info);
    })

    msgCenter.fire('carInfo',{info:'新型汽车上市!'})
    msgCenter.fire('catInfo',{info:'白色猫咪'})
    msgCenter.fire('foodInfo',{info:'今天有新的菜谱'})

  </script>

例2:

      售楼处卖房子。很多的想买房子的人来咨询房子的事情,但是现在有些房子还需要等待最终的结果,打算买房子的人就把自己的个人信息都给了售楼处的人,售楼处的人等这些房子有了消息,统一打电话告诉打算房子人他们想要知道的消息,就不用每次都给来一趟售楼处来知道关于当前房子的事。

    // 售楼处
    var saleOffices = {}
    // 保存需要买房的登记的用户(订阅者)
    saleOffices.clientList = []
    // 根据订阅相同类别的房的订阅者分类保存起来
    saleOffices.listen = function (key, fn) {
      if (!this.clientList[key]) {
        this.clientList[key] = []
      }
      this.clientList[key].push(fn)
    }
    // 发布消息给订阅者
    saleOffices.trigger = function () {
      // [].shift.call(arguments) arguments类数组转化为数组
      var key = [].shift.call(arguments)
      fns = this.clientList[key]
      // 不存在要通知的订阅者
      if (!fns || fns.length === 0) {
        return false
      }
      for (var i = 0, fn; fn = fns[i++];) {
        fn.apply(this, arguments)
      }
    }

    saleOffices.listen('squareMeter88', function (price) {
      console.log('价格:' + price);
    })
    saleOffices.listen('1000ping', function (price) {
      console.log('价格:' + price);
    })

    saleOffices.trigger('squareMeter88', 20000)
    saleOffices.trigger('1000ping', 40000)

【策略模式】:

  • 主要用于多种状态或策略需要进行选择的时候,将所有选择都封装在一起,只给外部暴露必要的接口。
  • 使用场景:定义一族算法族,将每个算法分别封装起来,让他们可以相互替换;避免冗长的 if-else 或 switch 分支判断。
 <script>
    var formStrategy = (function(){
      var strategy = {
        notEmpty:function(value){
          return value.length ? '' : '请填写内容'
        },
        isNumber:function(value){
          var reg = /^[0-9]+(\.[0-9]+)?$/
          return reg.test(value) ? '' : '请填写一个数字'
        },
        isPhone:function(value){
          // 010-12345678  0033-1234567
          var reg = /^\d{3}-\d{8}$|^\d{4}-\d{7}$/
          return reg.test(value) ? '' : '请输入正确的电话号码格式'
        }
      }

      // 向外暴露接口
      return {
        validate:function(type,value){
          value = value.replace(/^\s+|\s+$/,'')
          return strategy[type] ? strategy[type](value) : '没有这个检测方法,请添加一个'
        },
        addStrategy:function(type,fn){
          if(strategy[type]){
            return '这个方法已经存在'
          }else{
            strategy[type] = fn
          }
        }
      }
    })()
    // 检测
    window.onload = function(){
      // 添加检测规则
      formStrategy.addStrategy('isEmail',function(value){
          var reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
          return reg.test(value) ? '' : '请输入一个正确的邮箱地址'
        })

      var oInput = document.querySelector('input')
      oInput.onchange = function(){
        var result
        result = formStrategy.validate('notEmpty',this.value) || 
                //  formStrategy.validate('isNumber',this.value) ||
                formStrategy.validate('isEmail',this.value)
                 '通过检测'
        console.log(result);
        
      }
    }
  </script>

<body>
  请输入:<input type="text">
</body>

 例2、

1.公司发奖金,根据不同等级奖金比例也是不同,为了判断每一个人应该根据等级和等级所对应的倍数得到的奖金,写出下面的代码,需要计算的部分封装成了类,Bonus 作为了Context 拥有了执行策略的能力也就是执行这些算法类。
2.定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对 Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某一个进行计算。

<script>
    var strategies = {
      'S': function (salary) {
        return salary * 4
      },
      'A': function (salary) {
        return salary * 3
      },
      'B': function (salary) {
        return salary * 2
      }
    }
    var Bonus = function (level, salary) {
      return strategies[level](salary)
    }
    console.log(Bonus('S', 20000));
    console.log(Bonus('A', 10000));
  </script>

【迭代器模式】:

迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素(遍历集合对象),而又不暴露该对象的内部表示。

使用场景:

1、访问一个聚合对象的内容而无须暴露它的内部表示。
2、需要为聚合对象提供多种遍历方式。 
3、为遍历不同的聚合结构提供一个统一的接口。

    // hasNext():判断迭代是否结束,返回Boolean
    // next():查找并返回下一个元素
    const arr = [1, 'red', false, 3.14]

    function Iterator(item) {
      this.item = item
      this.index = 0
    }
    Iterator.prototype = {
      hasNext: function () {
        return this.index < this.item.length
      },
      next: function () {
        return this.item[this.index++]
      }
    }
    const iterator = new Iterator(arr)
    while (iterator.hasNext()) {
      console.log(iterator.next());
    }

    // 数字区间进行迭代
    function Range(start, end) {
      return {
        [Symbol.iterator]: function () {
          return {
            next() {
              if (start < end) {
                return {
                  value: start++,
                  done: false
                }
                return {
                  done: true,
                  value: end
                }
              }
            }
          }
        }
      }
    }
    for (num of Range(1, 6)) {
      console.log(num);
    }
  </script>

【命令模式】:

  •  命令模式:将请求以命令的形式包裹在对象中,这个对象传递给调用者或理解为调用者寻找可以处理该命令的合适对象,这个对象会处理传过来命令。
  • 使用场景:命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等;将命令的发起者和执行者解耦。

例子:比如电视它具有开和关的功能,用代码来表示的话就是一个'电视类'有两个方法'开电视,'关电视'。一般执行的时候'电视.开电视',对应上面说的'主要解决来讲'开电视这个行为和开电视这个实现都是电视来做的两者是在一起。
在定义上:这时候遥控器出现了,'开/关电视'的行为请求。可以看做两个命令,并且将这两个命令单独写成对象现在就多了两个对象'开电视' 和'关电视'他们是一组命令,多了个'遥控器'可以调用这些命令,并且把这些命令给了电视,电视依旧还是有'开电视', '关电视'的方法,不过不是直接请求调用而是遥控器间接调用了。

以去饭店吃饭为例,我们只需关心菜单上的菜,而不会必须要知道是那个厨师做的菜,因为服务员会帮我们将菜单上的'指令' 和厨师进行匹配。

<body>
  <button id="btn1">点击按钮1</button>
  <button id="btn2">点击按钮2</button>
  <button id="btn3">点击按钮3</button>
  <script>
    // 获取到三个按钮
    var btn1 = document.querySelector('#btn1')
    var btn2 = document.querySelector('#btn2')
    var btn3 = document.querySelector('#btn3')
    /*
    需求:
        刷新菜单目录,
        增加子菜单,
        删除子菜单
        receiver:服务员根据顾客命令分配厨师做菜
        command:顾客对菜单的操作
        invoker:菜单
    */
    //  真正的执行者received
    const MenuBar = {
      refresh() {
        console.log('刷新菜单!!');
      }
    }
    const subMenu = {
      add() {
        console.log('增加菜单');
      },
      delete() {
        console.log('删除菜单');
      }
    }
    // 把这些方法拆分为单独的类,并且拆分后的指令需要实现统一的方法,
    // 方便统一调用
    class BaseCommand {
      constructor(receiver) {
        if (new.target === BaseCommand) {
          throw new Error('这个类不能被实例化')
        }
        this._receiver = receiver
      }
      // 每个指令重写这个方法
      execute() {

      }
    }
    // MenuBar方法只有一个拆分为一个指令类
    class refersCommand extends BaseCommand {
      constructor(receiver) {
        super(receiver)
      }
      execute() {
        this.receiver.refresh()
      }
    }
    // subMenu两个方法拆分为两个指令类
    class addCommand extends BaseCommand {
      constructor(receiver) {
        super(receiver)
      }
      execute() {
        this.receiver.add()
      }
    }
    class delCommand extends BaseCommand {
      constructor(receiver) {
        super(receiver)
      }
      execute() {
        this.receiver.delete()
      }
    }
    // 调用者将这些指令与receiver进行匹配
    function setCommand(btn, command) {
      btn.onclick = function () {
        command.execute()
      }
    }
    // 声明指令,将指令与原始对象进行组合
    const referCommand = new refersCommand(MenuBar)
    setCommand(btn1, referCommand)
    const addcommand = new addCommand(subMenu)
    setCommand(btn2, addcommand)
  </script>
</body>

【适配器模式】:

  • 适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。
  • 使用场景:适配器模式用于补救设计上的缺陷,将不兼容的接口变得兼容;封装有缺陷的接口设计;统一多个类的接口设计;替换依赖的外部系统;兼容老版本接口;适配不同格式的数据。

百度地图和谷歌地图的渲染:

<script>
    var gooleMap = {
      show: function () {
        console.log('渲染谷歌地图');
      }
    }
    // 存在show方法时
    // var baiduMap = {
    //   show:function(){
    //     console.log('渲染百度地图');
    //   }
    // }
    var renderMap = function (map) {
      if (map.show instanceof Function) {
        map.show()
      }
    }
    renderMap(gooleMap) //输出:渲染谷歌地图
    renderMap(baiduMap) //输出:渲染百度地图
    // ----------------
    // 当百度地图没有show方法时
    var baiduMap = {
      display: function () {
        console.log('渲染百度地图');
      }
    }
    // 构造一个装饰器函数
    // 更换两个同一个功能的插件恰好他们的实现方法名一样
    var baiduMapAdapter = {
      show: function () {
        return baiduMap.display()
      }
    }
    renderMap(gooleMap) //输出:渲染谷歌地图
    renderMap(baiduMapAdapter) //输出:渲染百度地图
  </script>

【职责链模式】:

  • 链模式是实现链式调用的主要方法,通过在自身方法中返回自身的方式,在一个对象连续多次调用自身的方法时可以简化。
<script>
    var obj = {
      a:function(){
        console.log('aaa');
        return this
      },
      b:function(){
        console.log('bbb');
        return this
      }
    }
    // 链式执行
    obj.a().b()
  </script>

【模板方法模式】:

  • 模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法和封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
  • 在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来实现。
  • 使用场景:一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;子类中公共的行为应被提取出来并集中到一个公共父类中的避免代码重复。
 <script>
    // 父类--封装了子类的执行顺序
    abstract class Drinks {
      firstStep() {
        console.log('烧开水');
      }
      abstract secondStep();
      thirdStep() {
        console.log('倒入杯子');
      }
      abstract fourthStep();
      drink() {
        this.firstStep();
        this.secondStep();
        this.thirdStep();
        this.fourthStep();
      }
    }

    // 子类--通过继承父类,继承整个执行顺序,并重写父类的方法
    class Tea extends Drinks {
      secondStep() {
        console.log('泡茶叶');
      }
      fourthStep() {
        console.log('加柠檬');
      }
    }

    class Coffee extends Drinks {
      secondStep() {
        console.log('泡咖啡');
      }
      fourthStep() {
        console.log('加糖块');
      }
    }

    // 实现
    const tea = new Tea();
    tea.drink();
    const coffee = new Coffee();
    coffee.drink();
</script>

【访问者模式】:

  • 访问者模式(数据访问对象模式)主要用来抽象和封装一个对象来对数据源进行访问和存储,这样可以方便对数据的管理,以及避免数据间的重复,覆盖等问题出现。
<script>
    var DataVisitor = function(nameSpace,splitSign){
      this.nameSpace = nameSpace
      this.splitSign = splitSign || '|'
    }
    DataVisitor.prototype = {
      status :{
        SUCCESS:0,
        FAILURE:1,
        OYERFLOWER:2,
        TIMEOUT:3
      },
      getKey:function(key){
        return this.nameSpace + this.splitSign + key
      },
      set:function(key,value,cbFn,expireTime){
        var status = this.status.SUCCESS
        key = this.getKey(key)
        expireTime = typeof expireTime === 'number' ? expireTime + new Date().getTime() : -1

        try {
          window.localStorage.setItem(key,expireTime + this.splitSign + value)
        } catch (error) {
          status = this.status.OYERFLOWER
        }

        cbFn && cbFn.call(this,status,key,value)
        return value
      },
      get:function(key,cbFn){
        key = this.getKey(key)
        var status = this.status.SUCCESS
        var value = window.localStorage.getItem(key)

        if(value){
          var index = value.indexOf(this.splitSign),
          time = value.slice(0,index)

          if(time > new Date().getTime() || time == -1){
            value = value.slice(index + this.splitSign.length)
          }else{
            value =null
            status = this.status.TIMEOUT
            window.localStorage.removeItem(key)
          }
        }else{
          status = this.status.FAILURE
        }

        cbFn && cbFn.call(this,status,key,value)
        return value
      },
      remove:function(key,cbFn){
        var status = this.status.FAILURE
        key = this.getKey(key)
        value = window.localStorage.getItem(key)

        if(value){
          value.slice(value.indexOf(this.splitSign) + this.splitSign.length)
          window.localStorage.removeItem(key)
          status = this.status.SUCCESS
        }

        cbFn && cbFn.call(this,status,key,value)
      }
    }

    var learnInPro = new DataVisitor('learnInPro')

    learnInPro.set('aaa','123',function(status,key,value){
      console.log(status,key,value);
    })

    learnInPro.get('aaa',function(status,key,value){
      console.log(status,key,value);
    })

    learnInPro.remove('aaa',function(status,key,value){
      console.log(status,key,value);
    })

  </script>

【等待者模式】:

  • 通过对多个异步进程的监听,对未来事件进行统一管理
<script>
    function Water(){
      //存放异步事件
      var dfd = [] 
      //存放成功的回调
      var doneArr = [] 
      //存放失败的回调
      var failArr = []  
      this.when = function(){
        // 获取argements(是一个类数组对象)
        dfd = Array.prototype.splice.call(arguments)
        for(var i = dfd.length -1;i >=0;i--){
          var d = dfd[i]
          if(!d || d.rejected || d.resolved || !(d instanceof Defer)){
            dfd.splice(i,1)
          }
        }
        return this
      }
      // 成功之后执行的函数,相当于Promise的.then
      this.done = function(){
        var args = Array.prototype.slice.call(arguments)
        doneArr = doneArr.concat(args)
        return this
      }
      // 失败之后执行的函数
      this.fail = function(){
        var args = Array.prototype.slice.call(arguments)
        failArr = failArr.concat(args)
        return this
      }
      // 状态值
      this.Defered = function(){
        return new Defer()
      }
      var Defer = function(){
        // 状态值
        this.resolved = false
        this.rejected = false
      }
      Defer.prototype = {
        resolve:function(){
          this.resolved = true
          for(var i=0;i<dfd.length;i++){
            // 全部完成,就return出去
            if(!dfd[i].resolved){
              return
            }
          }
          // 全部完成之后执行成功的回调函数
          _exec(doneArr)
        },
        reject:function(){
          this.rejected =true
          // 有未完成则执行失败的回调函数
          _exec(failArr)
        }
      }

      function _exec(arr){
        for(var i = 0 ;i<arr.length;i++){
          arr[i] && arr[i]()
        }
      }
    }

    var waiter = new Water()
    var async1 = function(){
      var dfd = waiter.Defered()
      setTimeout(function(){
        console.log('async1 done');
        dfd.resolve()
      },1000)
      return dfd
    }
    var async2 = function(){
      var dfd = waiter.Defered()
      setTimeout(function(){
        console.log('async2 done');
        dfd.resolve()
      },2000)
      return dfd
    }

    waiter.when(async1(),async2()).done(function(){
      console.log('success');
    }).fail(function(){
      console.log('fail');
    })

  </script>

【备忘录模式】:

  • 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
  • 使用场景:分页控件;撤销组件。
<script>
    // 备忘录
    class Memento {
      constructor(content) {
        this.content = content;
      }
      getContent() {
        return this.content;
      }
    }
    // 备忘录列表
    class CareTaker {
      constructor() {
        this.list = [];
      }
      add(memento) {
        this.list.push(memento);
      }
      get(memento) {
        return this.list[index];
      }
    }
    // 编辑器
    class Editor {
      constructor() {
        this.content = null;
      }
      setContent(content) {
        this.content = content;
      }
      getContent() {
        return this.content;
      }
      saveContentMemento() {
        return new Memento(this.content);
      }
      getContentFromMemento(memento) {
        this.content = memento.getContent();
      }
    }
    // 实例化
    let editor = new Editor();
    let careTaker = new CareTaker();

    editor.setContent('测试1');
    editor.setContent('测试2');
    careTaker.add(editor.saveContentMemento());
    editor.setContent('测试3');
</script>

【状态模式】:

  • 状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

  • 通常我们谈到封装,一般都会优先封装对象的行为,而不是对象的状态。但在状态模式中刚好相反,状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,所以 button 被按下的的时候,只需要在上下文中,把这个请求委托给当前的状态对象即可,该状态对象会负责渲染它自身的行为。

有一个电灯,电灯上面只有一个开关。当电灯开着的时候,此时按下开关,电灯会切换到关闭状态;再按一次开关,电灯又将被打开。同一个开关按钮,在不同的状态下,表现出来的行为是不一样的。

<script>
    // 定义一个状态机
    var FSM = {
      off:{
        buttonWasPressed:function() {
          console.log('关灯');
          this.currentState = FSM.on;
        }
      },
      on:{
        buttonWasPressed:function() {
          console.log('开灯');
          this.currentState = FSM.off;
        }
      }
    }

    // 设置灯的初始状态
    var Light = function() {
      this.currentState = FSM.off;
      this.button = null;
    }

    Light.prototype.init = function() {
      var self = this;
      var button = document.createElement('button');
      this.button = document.body.appendChild(button);
      this.button.innerHTML = '开关';

      this.button.onclick = function() {
        // 把请求委托给状态机FSM
        self.currentState.buttonWasPressed.call(self);
      }
    }

    Light.prototype.setState = function (newState) {
      this.currentState = newState;
    }
    
    const light = new Light();
    light.init();
</script>

【中介者模式】:

  • 中介者模式的主要作用是解除对象之间的强耦合关系,通过增加一个中介者,让所有的对象通过中介者通信,而不是相互引用,所以当一个对象发生改变时,只需要通知中介者对象即可。
  • 中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(依赖关系)转换成一对多(星状关系)。原本一个对象要跟n个对象交互,现在只需要跟一个中介对象交互,从而最小化对象间的交互关系,降低了代码复杂度,提高了代码的可读性和可维护性。
  • 使用场景;系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象;想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
<script>
    class A {
      constructor() {
        this.number = 0;
      }
      setNumber(num, m) {
        this.number = num;
        if(m) {
          m.setB()
        }
      }
    }
    class B {
      constructor() {
        this.number = 0;
      }
        setNumber(num, m) {
          this.number = num;
          if(m) {
            m.setA();
          }
        }
    }
    // 中介者
    class Mediator {
      constructor(a,b) {
        this.a = a;
        this.b = b;
      }
      setA() {
        let number = this.b.number;
        this.a.setNumber(number*10);
      }
      setB() {
        let number = this.a.number;
        this.b.setNumber(number / 10);
      }
    }
    let a = new A();
    let b = new B();
    let m = new Mediator(a, b);
    a.setNumber(10, m);
    b.setNumber(10, m);
</script>

【解释器模式】:

  • 给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
  • 这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式常被用在 babel、sass、less 解析、符号处理引擎等。
  • 使用场景:可以将一个需要解释执行的语言中的句子,表示为一个抽象语法树;一些重复出现的问题可以用一种简单的语言来进行表达;一个简单语法需要解释的场景。
<script>
    // 解释器
    class TerminaExpression {
      constructor(data) {
        this.data = data;
      }

      interpret(context) {
        if(context.includes(context)) {
          return true;
        }
        return false;
      }
    }

    // 或 组合表达式
    class OrExpression {
      constructor(expr1, expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
      }

      interpret(context) {
        return this.expr1.interpret(context) || this.expr2.interpret(context);
      }
    }

    // 与  组合表达式
    class AndExpression {
      constructor(expr1, expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
      }

      interpret(context) {
        return this.expr1.interpret(context) && this.expr2.interpret(context);
      }
    }

    const robert = new TerminalExpression('Robert')
    const john = new TerminalExpression('John')
    const isMale = new OrExpression(robert, john)
    console.log(`John is male ? ${isMale.interpret('john')}`) // John is male ? true

    const julie = new TerminalExpression('Julie')
    const married = new TerminalExpression('Married ')
    const isMarriedWoman = new AndExpression(julie, married)
    // Julie is a married women ? true
    console.log(`Julie is a married women ? ${isMarriedWoman.interpret('Married Julie')}`) 
</script>

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值