javascript 设计模式

194 阅读28分钟

导语

关于javascript 的设计模式 ,相信很多小伙伴都知道 ,在实际开发中 ,也或多或少的用到了 其中的某几种个设计模式 , 文会详细解释关于 个设计模式的使用场景 和思想. 话不多说 开讲

设计模式的几大类型

  1. 创建型设计模式

    • 简单工厂模式
    • 工厂方法模式
    • 抽象工厂模式
    • 建造者模式
    • 原型模式
    • 单例模式
  2. 结构型设计模式

    • 外观模式
    • 适配器模式
    • 代理模式
    • 装饰者模式
    • 桥接模式
    • 组合模式
    • 享元模式
  3. 行为型设计模式

    • 模板方法模式
    • 观察者模式
    • 状态模式
    • 策略模式
    • 职责链模式
    • 命令模式
    • 访问者模式
    • 中介者模式
    • 备忘录模式
    • 迭代器模式
    • 解释器模式
  4. 技巧型设计模式

创建型设计模式

创建型设计模式是一类处理对象创建的设计模式,通过某种方式控制对象的创建来避免基本对象创建时刻能导致设计上的问题或增加设计上的复杂度

简单工厂模式

简单工厂模式(Simple Factory): 又叫静态工厂方法,由一个工厂对象决定创建某一种产生对象类的实例.主要用来创建同一类对象

例子


function createPop(type,text){
  //创建一个对象,并对对象扩展属性和方法
  let o = new Object();
  o.content = text;
  //公共的显示的方法 ,
  o.show = function(){

  }
  if(type == 'alert'){
    // 警示框一系列操作
  }
  if(type == 'prompt'){
    //提示框 一系列操作
  }

  if(type == 'confirm'){
    // 确认框 一系列操作
  }

}

let userNameAlert = create('alert','你来咬我呀')

缺点

通过对象包装(寄生方式)创建的都是一个新的个体,方法不能共用 使用场景通常也就限制在创建单一对象

工厂方法模式

工厂方法模式(Factory Method): 通过对产品类的抽象使 其创建业务主要负责创建类产品的实例.

在创建对象的方式也避免了使用者与对象类的耦合,用户不必关系创建该对象的具体类,只需直接调用即可

例子

//  安全模式创建的 工厂类  避免使用时 忘记使用 new 关键字
let Factory = function(type,content){
 if(this instanceof Factory ){
    let s = new this[type](content)
    return s
 }else{
   return new Factory(type,content)
 }
}

Factory.prototype = {
  Java:function(content){
      /// 
  },
    
  JavaScript:function(content){
      this.content = content;
      (function(content){
        let div = document.createElement('div');
        div.innerHTML = content;
        div.style.border= '1px solid red';
        document.getElementById('container').appendChild(div)
      })(content)
  }

}

  // 使用  广告
let data = [
    {type:"Java",content:"Java 我是"},
    {type:"JavaScript",content:"JavaScript 我是"},
]

for(let i =0 ; i< 2;i++){
    Factory(data[i].type,data[i].content)
}

抽象工厂模式

抽象工厂模式(Abstract Factory) :通过对类的工厂抽象使其业务对于产品类簇的创建,而不负责创建某一类产品的实例

抽象类是一种声明但不能使用的类, 既定义一个产品簇,并声明必备的方法,如果子类中没有重写就会抛出错误.

例子

// 抽象工厂方法
let VehicleFactory = function (subType, superType){
  //判断抽象工厂模式中是否有该抽象类
  if(typeof VehicleFactory[superType] === 'function'){
    //缓存类
    function F(){}
    //继承父类属性和方法
    F.prototype = new VehicleFactory[superType]();
    //将子类的 constructor 指向子类
    subType.constructor  = subType;
    //子类原型继承"父类"
    subType.prototype = new F()
  }else{
    //不存在该抽象类抛出错误
    throw new Error('未创键该抽象类')
  }
}

//小汽车抽象类

VehicleFactory.Car = function(){
  this.type = 'car';
}

VehicleFactory.Car.prototype ={
  getPrice :function(){
    return new Error('该抽象类不能调用')
  },
  getSpeed :function(){
    return new Error('该抽象类不能调用')
  }
}


//公交车抽象类

VehicleFactory.Bus = function(){
  this.type = 'bus';
}

VehicleFactory.Bus.prototype ={
  getPrice :function(){
    return new Error('该抽象类不能调用')
  },
  getPassengerNum :function(){
    return new Error('该抽象类不能调用')
  }
}


//使用 

//宝马汽车子类
let BMW = function(price,speed){
  this.price = price;
  this.speed = speed;
}
//抽象工厂实现对Car 抽象类的继承
VehicleFactory(BMW,'Car');
BMW.prototype.getPrice = function(){
  return this.price
}

BMW.prototype.getSpeed = function(){
  return this.speed
}


//宇通汽车子类
let YUTONG = function(price,passenger){
  this.price = price;
  this.passenger = passenger;
}
//抽象工厂实现对Bus 抽象类的继承
VehicleFactory(BMW,'Car');
BMW.prototype.getPrice = function(){
  return this.price
}

BMW.prototype.getPassengerNum = function(){
  return this.passenger
}


let bmw = new BMW(10000,100)
console.log(bmw.price)
console.log(bmw.speed)

缺点

该模式创建出的结果不是一个真实的对象实例,而是一个类簇,他制定了类的结构,这也就区别于简单工厂模式创建单一对象,工厂方法模式创建多类对象.当然由于Javascript 中不支持抽象化创建于虚拟方法,所以导致这种模式不能向其他面向对象语言中应用得那么广泛.

建造者模式

建造者模式(Builder):将一个复杂对象的构建层与其表示层互相分离,同样的构建过程可采用不同的表示.

通过工厂模式我们得到的都是对象实例或者类簇.然而建造者模式在创建对象时要更复杂一些,虽然其目的也是为了创建对象,但是他更多的是关心创建这个对象的整个过程,建造者模式更注重的是创建的细节.

例子

//创建一位人类
let Human = function(param){
  //技能
  this.skill = param && param.skill || '保密'
  //爱好
  this.hobby = param && param.hobby || '保密'
}

//人了原型方法
Human.prototype = {
  getSkill:function(){
    return this.skill
  },
  getHobby:function(){
    return this.hobby
  },
}


//实例化姓名类
let Named = function(name){
  if(name.indexOf(' ') > -1){
    this.FirstName = name.slice(0,name.indexOf(' '))
  }
 
}

//实例化职位类
let Work  = function(work){
  let that = this
  //构造器
  //构造函数中通过传入的职位特征来设置相应的职位和描述

  switch(work){
    case 'code':
      that.work = '工程师';
      that.workDesc = '没有bug的代码不是好代码'
      break;
    case 'UI':
      that.work = '设计师';
      that.workDesc = '设计就是艺术'
      break;
  }
}

let Person = function (name,work){
  //创建应聘者缓存对象
  let _person = new Human()
  _person.name = new Named(name)
  _person.work = new Work(work)
  return _person
}

var person = new Person('xiao ming','code');
console.log(person.skill) //保密
console.log(person.name.FirstName) //小

console.log(person)

缺点

这种方式对于整体对象类的拆分无形中增加了结构复杂性,如果对象粒度很小,或者模块间的复用率很低并且变动不大,我们最好还还是要创建整体对象.

原型模式

原型模式(Prototype): 用原型实例指向创建对象的类,使用于创建新的对象的类共享原型对象的属性以及方法.

javascript是基于原型链实现对象之间的继承

例子

 //图片轮播类
let LoopImages = function(imgArr,container){
  this.imageArrau = imgArr
  this.container = container
}

LoopImages.prototype = {

  //创建轮播图片
  createImage:function(){
    console.log('loopImage Create')
  },
  //切换下一张
  changeImage:function(){
    console.log('loopImage change')
  },
}

//上下滑动类
let SlideLoopImg = function(imgArr,container){
  //构造函数继承图片录播类
  LoopImages.call(this,imgArr,container)
}

SlideLoopImg.prototype = new LoopImages()
//重写继承方法
SlideLoopImg.prototype.changeImage = function(){
  console.log('slideLoopImg change')
}
var slideLoopImg = new SlideLoopImg('','container')
slideLoopImg.changeImage() //slideLoopImg change


不过原型模式更多的是用在对对象的创建上,比如创建一个实例对象的构造函数比较复杂,或者耗时比较长,或者通过创建多个对象来实现.此时我们最好不要用new 关键字去复制这些基类,但可以通过对这些对象属性或者方法进行复制来实现创建,这是原型模式最初的思想

//实现继承类
function prototypeExtend(...arg){
  let F = function(){}
  i = 0,
  len = arg.length

  for(;i< len; i++ ){
    for(let j in arg[i]){
      F.prototype[j] = arg[i][j]
    }
  }
  return new F()
}


let penguin = prototypeExtend({
  speed:20,
  swim:function(){
    console.log('游泳速度',this.speed)
  }
},{
  run:function(speed){
    console.log('奔跑速度',speed)
  }
})


penguin.swim()
penguin.run(10)


单例模式

单例模式(Singleton) 又被称为单体模式,是只允许实例化一些的对象类.有事我们也用一个对象来规划一个命名空间,井井有条地管理对象上的属性和方法.

命名空间就是人们说的namespace ,有人也叫他名称空间.为了让代码更易懂,人们常常用单词或者拼音定义变量或者方法, 不同人定义的名称可能重复,需要命名空间来约束每个人定义的变量.

例子

  //小型代码库

let A = {
    Util:{
      util_method1:function(){},
      util_method1:function(){},
    },
    
    Tool:{
      tool_method1:function(){},
      tool_method1:function(){},
    },
    
    Ajax:{
      get:function(){},
      post:function(){}
    }
    
    // ... 其他
}

关于静态变量都习惯使用大写

在单例中缓存在一种延迟创建的形式,有人也称为'惰性创建'

//惰性单例

let lasySingle = (function(){
//单例实例引用
let _instance = null;
//单例
function Single(){
  return {
    publiceMethod:function(){},
    puliceProperty:'1.1'
  }
}
//获取单例对象接口
return function(){
  // 如果不存在 创建实例
  if(!_instance){
    _instance = Single()
  }
    // 返回单例
  return _instance
}
})();

console.log(lasySingle().puliceProperty) //1.1

结构型设计模式

结构新设计模式关注与如何将类或对象组合成更大,跟复杂的结构,以简化设计

外观模式

外观模式(Facade): 为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口的访问更容易.在Javascript中有时也会用于对底层结构兼容性做统一封装来简化用户使用.

例子

//简版样式设置
let A = {
    g:function(id){
      return document.getElementById(id)
    },
    
    css:function(id,key,value){
      document.getElementById(id).style[key] = value
    },
    
    attr:function(id,key,value){
      document.getElementById(id)[key] = value
    },
    
    on:function(id,type,fn){
      document.getElementById(id)['on'+type] = fn
    }

}

A.css('box','background','red')

适配器模式

适配器模式(Adapter):将一个类(对象)的接口(方法或者属性)转化成另一个接口,以满足用户需求,使类(对象)之间接口的不兼容性问题通过适配器得以解决

例子

 let arr = ['Javascript','book','前端编程','9月'] 

function arrToAdater(arr){
  return {
    name:arr[0],
    type:arr[1],
    title:arr[2],
    data:arr[3],
  }
}


let adapterData = arrToAdater(arr)
console.log(adapterData) // {data: "9月"
name: "Javascript"
title: "前端编程"
type: "book"}

与外观模式的区别

适配器模式通常会将对象拆分并重新包装,我们需要了解适配对象的内部结构

代理模式

代理模式(Proxy) :由于一个对象不能应用另一个对象,所以需要通过代理对象在这两个对象之间起到中介作用

例子

  1. JSONP
  2. img 的 src

缺点

代理模式无论在处理ll系统,对象之间的耦合度问题还是在解决系统资源开销问题,他都将构建出一个复杂的代理对象,增加系统的复杂度,同时也增加了一定的系统开销,当然有是这种开销往往是可接受的

装饰者模式

装饰者模式(Decorator) : 在不改变原对象的基础上,通过对其进行包装扩展(添加属性或者方法)使原有对象可以满足用户的更复杂需求

不需要了解对象原有的功能,并且对象原有的方法照样可以原封不动的使用

例子


let decorator  = function (input,fn){
  
  let input = document.getElementById(input)
  //若事件源已经绑定事件
  if(typeof input.click === 'function'){
    //缓存事件源原有回调函数
    let oldClickFn = input.onclick
    //为事件源定义新事件

    input.onclick = function(){
      //原回调
      oldClickFn()

      //新增回调
      fn()
    }
  }else{
    input.onclick = fn()
  }
}


decorator('address_input',function()[
  document.getElementById('text').style.display = 'none'
])

桥接模式

桥接模式(Bridge):在系统沿着多个维度变化的同时,又不增加其复杂度比已达到解耦

最主要的特点即是将实现层(绑定事件)与抽象层(UI逻辑)结构分离,使两个部分可以独立变化,主要是对结构之间的结构.

例子


//多维变量类


//运动单元
function Speed(x,y){
  this.x = x;
  this.y = y;
}

Speed.prototype.run = function(){
   console.log('动起来')
}
 
//着色单元
function Color(cl){
  this.color = cl
}
Color.prototype.draw = function(){
  console.log('绘制颜色')
}

  
//变形单元
function Shape(sp){
  this.shape = cl
}
Shape.prototype.change = function(){
  console.log('改变形状')
}


//实现一个球类
function Ball(x,y,c){
  this.speed = new Speed(x,y)
  this.color = new Color(c)
}
//同时做的事情
Ball.prototype.init = function(){
  this.speed.run()
  this.color.draw()
}

缺点

有时会造成卡法成本的增加,有时性能也会受到影响

组合模式

组合模式(Composite) :又称部分-整体模式,将对象组合成树形结构已表示'部分整体'的层次结构.组合模式使得用户对单个对象和组合对象的使用具有一致性

组合模式不仅仅是单层次的 ,也可以是多层次的, 最底层中的对象是没有子成员的 ,

/* Folder */ 
var Folder = function( name ){
    this.name = name;
    this.files = []; 
};
Folder.prototype.add= function( file ){ 
    this.files.push(file );
};
Folder.prototype.scan = function(){
    console.log( '开始扫描文件夹: ' + this.name );
    for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
        files file.scan();
    } 
};
/*File*/
var File = function( name ){
    this.name = name; 
};
File.prototype.add = function(){
    throw new Error( '文件下面不能再添加文件' );
};
File.prototype.scan = function(){
    console.log( '开始扫描文件: ' + this.name );
};
/*创建一些文件夹和文件对象, 并且让它们组合成一棵树,这棵树就是我们 F 盘里的 现有文件目录结构*/
var folder = new Folder( '学习资料' ); 
var folder1 = new Folder( 'JavaScript' ); 
var folder2 = new Folder ( 'jQuery' );
var file1 = new File( 'JavaScript 设计模式' );
var file2 = new File( '精通 jQuery' );
var file3 = new File('重构与模式' );
folder1.add( file1 ); 
folder2.add( file2 );
folder.add( folder1 ); 
folder.add( folder2 ); 
folder.add( file3 );

/*现在的需求是把移动硬盘里的文件和文件夹都复制到这棵树中,假设我们已经得到了这些文件对象*/
var folder3 = new Folder( 'Nodejs' );
var file4 = new File( '深入浅出 Node.js' ); 
folder3.add( file4 );
var file5 = new File( 'JavaScript 语言精髓与编程实践' );

/*接下来就是把这些文件都添加到原有的树中*/
folder.add( folder3 ); 
folder.add( file5 );


享元模式

享元模式(Flyweight) :运用共享技术有效的支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销

享元模式主要还是对其数据,方法共享分离,它将数据和方法分成内部数据,内部方法,外部数据和外部方法.内部方法与内部数据值得是相似或者共有的数据和方法,所有将这一部分提取出来减少开销,以提高性能

例子

 
  
    let FlyWeight = {
      moveX: function(x){
        this.x = x;
      },
      moveY: function(y){
        this.y = y ;
      }
    }
    //人 
    let Player = function(x,y,c){
      this.x = x;
      this.y = y;
      this.color = c;
    }

    Player.prototype = FlyWeight
    Player.prototype.changeC = function(c){
      this.color = c
    }
    
    //精灵
    let Spirit = function(x,y,r){
      this.x = x;
      this.y = y;
      this.r = r;
    }

    Spirit.prototype = FlyWeight
    Spirit.prototype.changeR = function(r){
      this.r = r
    }


    //实例一个人

    let player1 = new Player(5,6,'red')
    console.logp(player1)

    //让人动起来

    player1.moveX(20)
    player1.moveY(30)
    
      //实例一个精灵
    let spirit1 = new Spirit(5,6,'red')
    console.logp(spirit1)

    //让人动起来

    spirit1.moveX(20)
    spirit1.moveY(30)
    spirit1.changeR(20)

缺点

享元模式的应用目的是为了提高程序的执行小路与系统的性能, 当在一些小的程序中,性能与内存的小号对程序的性能影响不大时,强行应用享元模式而引入的代码逻辑,往往会收到负效应.

行为型设计模式

行为型设计模式用于不同对象之间职责划分或算法抽象,行为型设计模式不仅仅涉及类和对象,还涉及类或对象之间的交流模式并加以实现

模板方法模式

模板方法模式(Template Method):父类中定义一组操作算法骨架,而将一些实现步骤延迟到子类中,使得子类可以不改变父类的算法结构的同时可重新定义方法中某些实现步骤

模板方法的核心在于对方法的重用

例子

//模板类
let Alert = function(data){
  //没有数据直接返回
  if(!data){
    return
  }
  //设置内容
  this.content = data.content;
  //创建提示面板
  this.panel = document.createElement('div')
  //创建提示内容组件
  this.contentNode = document.createElement('p')
  //确定按钮
  this.confirmBtn = document.createElement('span')

  //关闭按钮

  this.closeBtn = document.createElement('b')
  // 添加类
  this.panel.className = 'a-close'
  this.closeBtn.className = 'a-confirm'
  this.confirmBtn.className = 'alert'

  //按钮添加文案
  this.confirmBtn.innerHTML = data.confirm || '确认'

  //提示内容
  this.contentNode.innerHTML = this.content
  //成功 失败方法
  this.success = data.success || function(){}
  this.fail = data.fail ||function(){}
}

Alert.prototype = {
  //创建方法
  init:function(){
    this.panel.appendChild(this.closeBtn)
    this.panel.appendChild(this.contentNode)
    this.panel.appendChild(this.confirmBtn)

    //插入页面
    document.body.appendChild(this.panel);
    //绑定事件
    this.bindEvent();
    //显示

    this.show()
  },

  bindEvent:function(){
    let me = this
    this.closeBtn.onclick = function(){
      //执行关闭
      me.fail()
      //隐藏
      me.hide()
    }

  },
  hide:function(){
    this.panel.style.display = 'none'
  },
  show:function(){
    this.panel.style.display = 'block'
  }
}


//右侧按钮提示框

let RightAlert = function(data){
  Alert.call(this,data)
  //给确认按钮添加 类样式
  this.confirmBtn.className = this.confirmBtn.className + ' right'
}

RightAlert.prototype = new Alert()

观察者模式

观察者模式(Observer): 又被称作发布-订阅者模式或消息机制,定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合

对于每一个订阅者来说,其自身可以使消息的发出者,也可以是消息的执行者,这都依赖于调用观察者对象的三种方法(订阅消息,注销消息,发布消息)中的哪一种

例子

let Observer = (function(){
  //防止篡改
  let _messages= {}

  return {
    //注册信息接口
    regist:function(type,fn){
      //不存在创建改消息类型
      if(typeof _messages[type] === 'undefined'){
        _messages[type] = [fn]
      }else{
        _messages[type].push(fn)
      }
    },
    //发布信息接口
    fire:function(type,args){
      //没有注册直接返回
      if(!_messages[type]){
        return
      }

      let events = {
        type, //
        args:args || {} //携带数据
      }

      let i = 0;
      let len = _messages[type].length

      for(;i < len; i++){
        //依次执行注册的消息对应的动作序列
        _messages[type][i].call(this,events)
      }
    },
    //移除信息接口
    remove:function(type,fn){
      //如果消息队列存在

      if(_messages[type] instanceof Array){
        let i =  _messages[type].length - 1
        for(;i>= 0; i--){
          //如果存在该动作则在消息动作序列中移除相应动作
          _messages[type][i] === fn && _messages[type].splice(i,1)
        }
      }
    }
  }
})()


Observer.regist('test',function(e){
  console.log(e.type,e.args.msg)
})

Observer.fire('test',{msg:'参数'})


// 正式开始使用

let Student = function(result){
  let that = this;
  //学生回答结果
  that.result = result
  //回答动作
  that.say = function(){
    console.log(that.result)
  }
}
//回答问答方法
Student.prototype.answer = function(question){
  //注册参数问题
  Observer.regist(question,this.say)
}

  //睡觉不能回答问题了
Student.prototype.sleep = function(question){
  console.log(this.result + ' ' + question + ' 已被注销')
  //取消监听
  Observer.remove(question,this.say)
}


let  Teacher = function(){}

Teacher.prototype.ask = function(question){
  console.log('问题是 '+ question);
  //发布提问
  Observer.fire(question)
}


let student1 = new Student('学生1回答问题')
let student2 = new Student('学生2回答问题')
let student3= new Student('学生3回答问题')


student1.answer('什么是设计模式')
student1.answer('说说观察者模式')
student2.answer('什么是设计模式')
student3.answer('什么是设计模式')
student3.answer('说说观察者模式')


student3.sleep('什么是设计模式')


let teacher = new Teacher()

teacher.ask('什么是设计模式')
teacher.ask('说说观察者模式')


    // 结果 
  // 
  学生3回答问题 什么是设计模式 已被注销
  // 问题是 什么是设计模式
  // 学生1回答问题
  // 学生2回答问题
  // 问题是 说说观察者模式
  // 学生1回答问题
  // 学生3回答问题

状态模式

状态模式(State):当一个对象的内部状态发生变化改变时,会导致其行为的改变,这看来像是改变了对象

状态模式即是解决程序中臃肿的分支判断语句额问题,将每个分支优化为一种状态独立出来,方便每种状态的管理又不至于每次执行时遍历所有分支. 最终的目的既是简化分支判断流程

例子

 // 创建超级玛丽状态类
let MarryState = function(){
  //私有变量

  let _currentState = {}
  let states = {
    jump:function(){
      console.log('jump')
    },

    move:function(){
      console.log('move')
    },

    shoot:function(){
      console.log('shoot')
    },

    squat:function(){
      console.log('squat')
    },
      
    
  }

  //动作控制类

  let Action = {
    //改变状态方法
    changeState:function(){
      let arg = arguments;
      // 重置内部状态
      _currentState = {}

      if(arg.length){
        //遍历动作

        for(let i = 0, len = arg.length; i < len ; i++){  
          //向内不状态中添加动作
          _currentState[arg[i]] = true
        }
      }
      //返回动作控制类

      return this
    },


    goes:function(){
      console.log('触发一次动作')

      //遍历内部状态保存的动作
      for(let i in _currentState){
        states[i] && states[i]()
      }

      return this
    }
  }


  return {
    change: Action.changeState,
    goes: Action.goes
  }
}


let marry  = new MarryState()

marry
.change('jump','shoot')
.goes()
.goes()
.change('shoot')
.goes()



// 结果 

// 触发一次动作
// jump
// shoot
//  触发一次动作
//  jump
//  shoot
//  触发一次动作
//  shoot

策略模式

策略模式(Strategy): 将定义的一组算法封装起来,使其相互之间可以替换.封装的算法具有一定的独立性,不会随客户端变化而变化

结构上看,他与状态模式很像,也是在内部封装一个对象,然后通过返回的接口对象实现对内部对象的调用,不同点是策略模式不需要管理状态, 状态间没有依赖关系,策略之间可以相互替换,在策略对象内部保存的是相互独立的一些算法

对于策略模式的有点可以归纳为3点

    1. 策略模式封装了一组代码簇,并且封装的代码相互之间独立,便于对算法的重复引用,提高了算法的复用性
    1. 策略模式与继承相比,在类的继承中继承的方法是被封装在类中,因此当需求很多时,不得不创建多种类, 会导致算法与使用者耦合在一起, 不利于算法的独立演化,并且在类的外部改变类的算法难度是极大的
    1. 同状态模式一样,策略模式也是一种优化分支判断语句的模式,采用策略模式对算法封装使得算法更利于维护

实现

    //价格策略对象
let PriceStrategy= function(){
  //内部算法对象
  let stragtegy = {
    // 100 返 30 
    return30:function(price){
      return +price + parseInt(price / 100 ) * 30
    },

     
     return50:function(price){
      return +price + parseInt(price / 100 ) * 50
    },
    //8折
    percent80:function(price){
      return price * 100 * 80 / 10000
    },

    percent50:function(price){
      return price * 100 * 50 / 10000
    }
  }
  //策略算法调用接口
  
  return function(algorithm, price){
    return stragtegy[algorithm] && stragtegy[algorithm](price)
  } 
}();

var price  = PriceStrategy('return50','334.34')
console.log(price) //484.34

缺点

由于选择哪种算法的决定权在用户,所已对用户来说就必须了解每种算法的实现,这就增加了用户对策略对象的使用成本,其次,由于每种算法间相互独立,这样一些复杂的算法处理相同逻辑的部分无法实现分享,这就造成了一些资源浪费, 可以通过享元模式解决

职责链模式

职责链模式(Chain Responsibiliy) :解决请求的发送者与请求的接受者之间的耦合,通过职责链上的多个对象对分解请求流程,实现请求在多个对象之间的传递,知道最后一个对象完成请求的处理

对于职责链上的每一个对象来说,他都可能是请求的发起者也可能是请求的接受者.通过这样的方式不仅仅简化原对象的复杂度,而且解决原请求的发起者与原请求的接受者之间的耦合.

例子


      
      
  }


  //提示框组件

  let createSug = function(data,dom){
    let i = 0;
    let len = data.length
    let html = ''

    for(;i < len; i++){
      html += `<li>${data[i]}</li>`
    }
    //显示
    dom.parentNode.getElementByTagName('ul')[0].innerHtML = html
  }

//校验组件
  let createValidataResult = function(data,dom){
    dom.parentNode.getElementByTagName('ul')[0].innerHtML = data
  }

缺点

对于职责链上的每一个对象不一定都能参请求的传递,有时会造成一丝资源的浪费,并且多个对象参与请求的传递, 这在代码调试时增加了调试的成本

命令模式

命令模式(Command): 将请求与实现解耦并封装成独立的对象,从而使不同的请求对用户端的实现参数化

命令模式是将请求模块与实现模块解耦 , 命令模式是将执行的命令封装, 解决命令的发起者与命令的执行者之间的耦合.每一条命令实质上是一个操作, 所有的命令都被存储在命令对象中

例子

  let CanvasCommand = (function(){
    //获取元素
    let canvas = document.getElementById('canvas')

    ctx = canvas.getContext('2d');

    //内部方法对象
    let Action = {
      //填充颜色
      fillStyle : function(c){
        ctx.fillStyle = c;
      },

      //填充矩形
      fillRect:function(x,y,width,height){
        ctx.fillRect(x,y,width,height)
      },

      //描边色彩
      storkeStyle:function(c){
        ctx.storkeStyle =c
      },

       //填充字体
       fillText:function(text,x,y){
        ctx.fillText(text,x,y)
      },

      //开启路径
      beginPath:function(){
        ctx.beginPath()
      },

       //移动画笔触电
      moveTo:function(x,y){
        ctx.moveTo(x,y)
      },

       //画笔连线
       lineTo:function(x,y){
        ctx.lineTo(x,y)
      },
       //绘制弧线
       arc:function(x,y,r,begin,end,dir){
        ctx.arc(x,y,r,begin,end,dir)
      },
       //填充
       fill:function(){
        ctx.fill()
      },
       //描边
       stroke:function(){
        ctx.stroke()
      },


    }

    return {
        ///命令接口

      excute: function(msg){
        
        if(!msg){
          return
        }

        if(msg.length){
          //遍历执行
          for(let i = 0; i<msg.lenght; i++){
            arguments.callee(msg[i])
          }
        }else{
          //如果不是msg.params 不是一个数,将其转回为数组
          msg.param = Object.prototype.toString.call(msg.param) === '[object Array]' ? msg.param : [ msg.param]
          Action[msg.command].apply(Action,msg.param)
        }
      }
    }

  })();

  //使用

  CanvasCommand.excute([
    {command: 'fillStyle',param:'red'}
  ])

缺点

命令模式是对一些操作的封装,这就造成每次执行一次操作都要调用一次命令对象,增加了系统的复杂度

访问者模式

访问者模式(Visitor): 针对于对象结构中的元素,定义在不改变该对象的前提下访问结构中元素的新方法.

访问者模式解决数据与数据的操作方法之间的耦合,将数据的操作方法独立于数据, 使其可以自由化演变.可以自由修改操作方法适应操作环境,而不用修改原数据,实现操作方法的扩展.对于同一个数据,它可以被多个访问对象访问,极大增加了操作的灵活性.

例子

 //访问类

  let Visitor = (function(){
    return{
         //截取方法
      splice:function(){
        //从原参数的第二个参数开始算起

        let args = Array.prototype.splice.call(arguments,1) 
      //对第一个参数对象执行splice方法
        return Array.prototype.splice.apply(arguments[0],args) 
      
      },

      push:function(){
        //强化类数组对象,使其拥有length属性
        let len = arguments[0].lenght || 0
        //添加的数据从原参数的第二个参数算起
        let args = this.splice(arguments,1)

        //校正length 属性

        arguments[0].length = len + arguments.length - 1
        //对第一个参数对象执行push方法
        return Array.prototype.push.apply(arguments[0],args)
      },

      pop:function(){
        return Array.prototype.pop.apply(arguments[0]
        
        )
      }
    }
 

  })()


//使用

let a = new Object()
console.log(a.length) //'undefined' 
Visitor.push(a,1,2)
console.log(a) //{0: 1,1: 2 ,length: 2 }

中介者模式

中介者模式(Mediator): 通过中介对象封装一系列对象之间的交互,使对象之前不再相互引用,降低他们之间的耦合.有时中介者对象也可以改变对象之前的交互

同观察者模式一样,中介者模式的主要业务也是通过模块间或者对象间的复杂通讯,来解决模块间或对象间的耦合.对于中介者对象的本质是封装多个对象的交互,并且这些对象的交互一般都是在中介者内部实现

与外观模式的封装特性相比,中介者模式对多个对象交互的封装,且这些对象一般处于同一层面上,并且封装的交互在中介者内部,而外观模式封装的目的是为了提供更简单的易用接口,而不添加其他功能

观察者模式相比,观察者模式中的订阅是双向的,既可以是消息的发布者,也可以是消息的订阅者,而在中介者模式中,订阅者是单向的,只能回消息的订阅者.而消息统一由中介者对象发布,所有的订阅者对象间接的被中介者管理

例子

//中介者对象
let Mediator = (function(){
  // 消息对象
  let _msg = {}
  return {
    
    //订阅消息方法

    register:function(type,action){
      if(_msg[type]){
        _msg[type].push(action)
      }else{
        _msg[type] = []
        _msg[type].push(action)
      }
    },

    //发布消息方法

    send:function(type){
      if(_msg[type]){
        for(let i = 0; i< _msg[type].length; i++ ){
          _msg[type][i] && _msg[type][i]()
        }
      }
    }
  }
})()

//测试

Mediator.register('dome',function(){
  console.log('1111')
})

Mediator.send('dome')

备忘录模式

备忘录模式(Memento): 在不破坏对象的封装性前提下,在对象之外捕获并保存改对象内部的状态以便日后对象使用或者对象恢复到以前的某个状态.

在备忘录模式中,数据常常存储在备忘录对象的缓存器中,这样对于数据的读取必定要调用备忘录提供的方法,因此备忘录对象也是对数据缓存器的一次保护性封装,防止外界的直接访问,方便数据的管理,规范化外界对象对数据的使用.

例子

let Page = function(){
  let cache = {}

  return function(page,fn){
    if(cache[page]){
      
      fn && fn()
    }else{
      // 执行相关业务 

      cache[page] = data // 数据源
      fn && fn()
    }
  }
}

缺点

当数据量过大时,会严重占用系统提供的资源,这会极大降低系统性能.因此资源空间的限制是影响备忘录模式应用的一大障碍.

迭代器模式

迭代器模式(Iterator): 在不暴露对象内部结构的同时,可以顺序的访问聚合对象内部的元素

在开发中,迭代器极大的简化了代码中的循环语句,是代码结构清晰紧凑,

例子

//数组迭代器

let eachArray = function(arr,fn){
  let i = 0
  for(; i< arr.length; i++){
    if(fn.call(arr[i],i ,arr[i]) === false ){
      break
    }
  }
}

//对象迭代器

let eachObject = function(ojb,fn){
  for(let i in obj){
    if(fn.call(obj[i],i ,obj[i]) === false ){
      break
    }
  }
}

解释器模式

解释器模式(Interpreter): 对于一种语言,给出其文法表示形式,并定义一种解释器,通过使用这种解释器来解释语言中定义的句子.

文法就是一种语言中的语法描述工具

例子

  //xPath 解释器
let Interpreter = (function(){
  function getSublingName(node){
    //如果存在兄弟元素
    if(node.previousSibling){
      let name = '' //返回的兄弟元素名称
      let count = 1  // 紧邻兄弟元素相同名称的个数
      let nodeName = node.nodeName // 原始节点
      let sibling = node.previousSibling // 前一个兄弟节点
      
      //如果存在前一个兄弟节点
      while(sibling){
        //如果节点为元素, 且节点类型与前一个相同 且前一个兄弟元素名称存在
        if(sibling.nodeType == 1 && sibling.nodeType  === node.nodeType && sibling.nodeName){
          //如果节点名称相同
          if(nodeName == sibling.nodeName){
            name += ++count
          }else{
            //重置相同紧邻节点名称的个数
            count = 1
            name += '|' + sibling.nodeName.toUpperCase()
          }
        }

        // 向前获取前一个兄弟元素
        sibling = sibling.previousSibling

      }

      return name
    }else{
      return ''
    }
    
  }

  return function(node,wrap){
    //如果目标节点 等于 容器节点
    if(node == wrap){
      if(wrap.nodeType == 1){
        //路径数组输入容器节点名称
        path.push(wrap.nodeName.toUpperCase())
      }
      // 返回路径数组结果
      return path
    }
    //不等于
    if(node != wrap){
      //对当前结案的父节点执行遍历
      path = arguments.callee(node.parentNode,wrap)
    }else{ // 如果当前节点的父元素与容器节点相等
      if(wrap.nodeType == 1){
         //路径数组输入容器节点名称
         path.push(wrap.nodeName.toUpperCase())
      }

    }
    //获取元素的兄弟元素名称统计
    let getSublingNames = getSublingName(node)
    //如果节点为元素
    if(node.nodeType == 1){
      path.push(wrap.nodeName.toUpperCase() + getSublingNames)
    }


    return path
  }

})()

// 使用 
let path = Interpreter(document.getElementById('id1'))

技巧型设计模式

技巧型设计模式是通过一些特定技巧来解决组件的某些方面的问题

链模式

链模式(Operate of Responsibility): 通过在对象方法中将当前对象返回,实现对一个对象多个方法的链式调用.从而简化对该对象的多个方法多次调用

可参考 jQuery 主要思想为 方法最后返回当时实例

委托模式

委托模式(Entrust): 多个对象接受并处理同一个请求,他们将请求委托给另一个对象统一处理请求

委托模式还能处理一些内存外泄的问题

例子

ul.onclick = function(e){
    let e = e || window.event
    let tar = e.target || e.srcElement
    if(tar.nodeName.toLowerCase() == 'li'){
        tar.style.backgroundColor = 'red'
    }
}

数据访问对象模式

数据访问对象模式(Dataaccess Object-DAO) : 抽象和封装对数据源的访问与存储,DAO通过对数据源链接的管理方便对数据的访问与存储

数据访问对象(DAO)模式即是对数据库的操作,(如增删改查)进行封装

let BaseLocalStorage = function(preId,timeSign){
  //定义本次存储数据库前缀
  this.preId = preId
  //定义时间戳与存储数据之间的拼接父
  this.timeSign  = timeSign || '|-|'
}

BaseLocalStorage.prototype = {
  //操作状态
  status :{
    SUCCESS:0, //成功
    FALLURE:1, // 失败
    OVERFLOW:2,// 溢出
    TIMEOUT:3 // 过期
  },

  // 本地存储链接
  storage : localStorage || window.localStorage,

  //g获取本地存储数据库真实字段
  getKey:function(key){
    return this.preId + key
  },
  //修改数据

  set:function(key, value , callback ,time){
    //默认成功

    let status = this.status.SUCCESS
    //获取真实字段
    let key = this.getKey(key)
    try{
      time = new Date(time).getTime() || time.getTime()
    }catch(e){
      //默认一个月

      time  = new Date(time).getTime() + 1000 * 60 * 60 * 24 * 31
    }

    try{
      //数据库添加数据
      this.storage.setItem(key,time + this.timeSign + value)
    }catch(e){
      // 溢出

      status = this.status.OVERFLOW
    }


    // 
    callback &&callback.call(this,status,key, value)
  },

  get:function(key,callback){
    let status = this.status.SUCCESS
    let key = this.getKey(key)
    //默认值
    let value = null
    
    //时间戳与存储数据之间拼接父长度
    let timeSignLen = this.timeSign.length
    let that = this;
    let index;
    let time;
    let result;


    try{
      value = that.storage.getItem(key)
    }catch(e){
      // 失败结果为null

      result = {
        status: that.status.FALLURE,
        value:null,
      }
      callback && callback.call(that,result.status ,result.value)
    }


    //成功获取数据字符串

    if(value){
      //g获取起始位置

      index  =value.indexOf(that.timeSign)

      //获取时间戳
      time = +value.slice(0,index)

      // 如果时间过期
      if(new Date(time).getTime() > new Date().getTime() || time == 0){
        // 获取数据结果 拼接字符串后面的字符串
        value  = value.slice(index,timeSignLen)
      }else{
        // 过期则结果为null
        value = null 
        status = that.status.TIMEOUT
        that.remove(keyline-all)
      }
    }else{
      // 为获取数据字符串状态为失败状态
      status = that.status.FALLURE
    }

    //设置结果
    result = {
      status: status,
      value:value,
    }
    callback && callback.call(that,result.status ,result.value)

    return result
  },

  remove:function(key,callback){
    let status = this.status.FALLURE
    let key = this.getKey(key)
    let value = null

    try{
      value = this.storage.getItem(key)
    }catch(e){

    }

    if(value){
      try{
        this.storage.removeItem(key)
        status = this.status.SUCCESS
      }catch(e){}
    }

    callback && callback.call(this,status ,status > 0 ? null : value.slice(value.indexOf(this.timeSign) + this.timeSign.length))
  }

}

let LS= new BaseLocalStorage("LS___")

lS.set('a','Xiaoming' , function(){
  console.log(arguments)
})

节流模式

节流模式(Throttler): 对重复的业务逻辑进行节流控制,执行最后一次操作并取消其他操作,以提高性能

参考我的另一篇文章 防抖与节流

简单模板模式

简单模板模式(Simple template) : 通过格式化字符串拼凑出视图避免创建视图是大量的节点操作,优化内存开销

惰性模式

多行模式(Iayier) : 减少每次代码执行时的重复性的分支判断, 通过对对象重新定义来屏蔽原对像中的分支判断

惰性模式是一种拖延模式, 惰性模式又分为两种: 第一种是文件加载后立即执行对象方法来重新第一对象.第二种是当第一次使用方法是重新定义对象. 对于第一种方式,由于文件加载时执行,因此会占用一些资源,第二种方式由于第一次使用时重新定义对象,以值第一次执行时间增加

例子


 // 第一种
  let createXHR = (function(){
    if(typeof XMLHttpRequest != 'undefined'){
      return function(){
        return new XMLHttpRequest()
      }
    } else if(typeof ActiveXObject != 'undefined' ){
      return function(){

      }
    } else {
      return function(){
        throw new Error('no xhr')
      }
    }

  })()

  //第二种

  function createXHR (){
    if(typeof XMLHttpRequest != 'undefined'){
      createXHR =  function(){
        return new XMLHttpRequest()
      }
    } else if(typeof ActiveXObject != 'undefined' ){
      createXHR = function(){

      }
    } else {
      createXHR =  function(){
        throw new Error('no xhr')
      }
    }

参与者模式

参与者模式(participator): 在特定的作用域中执行给定的函数 , 并将参数原封不动的传递

参与者模式实质上是 两种技术的结晶: 函数绑定和函数柯里化

函数绑定 如 bind apply call.

函数柯里化即是将接收的多个参数的函数转化成接收一部分参数的新函数,余下的参数保存下来,当函数调用时,返回传入的参数与保存的参数共同执行的结果

例子

Function.prototype.bindFn = function bind(thisArg){
    if(typeof this !== 'function'){
        throw new TypeError(this + 'must be a function');
    }
    // 存储函数本身
    var self = this;
    // 去除thisArg的其他参数 转成数组
    var args = [].slice.call(arguments, 1);
    var bound = function(){
        // bind返回的函数 的参数转成数组
        var boundArgs = [].slice.call(arguments);
        // apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
        return self.apply(thisArg, args.concat(boundArgs));
    }
    return bound;
}


等待者模式

等待者模式(waiter) : 通过对多个异步进程监听,来触发未来发生的动作

参考 promise

例子

function Promise(executor){

    let self = this;
    self.status = 'pending'; 
    self.value = undefined;
    self.reason = undefined;
    function resolve(value){
        if( self.status === 'pending'){
            self.status = 'fulfilled';
            self.value = value;
        }
    }
    function reject(reason){
        if( self.status === 'pending'){
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    executor(resolve,reject);
}
Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this;
    if(self.status === 'fulfilled'){
        onFulfilled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise;


结尾

本文参考 <javascript设计模式>

前端界的小学!!!