前端面试五

25、列出你所知道的设计模式并简述

设计模式的定义:

● 设计模式是一套被反复使用多数人知晓的经过分类的、代码设计经验总结

使用设计模式的目的:

●  为了代码的可重用性可靠性、可维护性、更容易被他人理解

设计模式的分类:主要分为三大类

创建型模式:5种,Factory Method工厂方法模式、Abstract Factory抽象工厂模式、Singleton单例模式、Builder建造者模式、Prototype原型模式

结构型模式:7种,Adapter适配器模式、Decorator装饰模式、Proxy代理模式、Facade Pattern门面模式、Bridge桥梁模式、Composite合成模式、Flyweight享元模式。

行为型模式:11种,Strategy策略模式、Template Method模板方法模式、Observer观察者模式、Iterator迭代子模式、 Chain Of Responsibility责任链模式、Command命令模式、Memento备忘录模式、State状态模式、Visitor访问者模式、Mediator调停者模式、Interpreter解释器模式

 
面试被问到关于设计模式的知识时,可以拣最常用的作答,例如: 

1工厂模式:可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位置,防止代码重复。工厂模式解决了重复实例化的问题 ,但还有一个问题,那就是对象识别问题,因为根本无法 搞清楚他们到底是哪个对象的实例 。

function createPerson(name,age,job){
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.job = job;
    obj.printName = function(){
      console.log(this.name)
    }
    return obj;
}

let person1 = createPerson('ke',10,'teacher');
let person2 = createPerson('hua',20,'student');
console.log(person1['name']); //两种形式访问对象的属性
console.log(person1.name);

2)构造函数模式:解决了重复实例化的问题 ,又解决了对象识别的问题,该模式与工厂模式的不同之处在于:

1.构造函数方法没有显示的创建对象 (new Object());
 
2.直接将属性和方法赋值给 this 对象;
 
3.没有 renturn 语句

4.调用构造函数的时候使用new关键字

function Person(name,age,job){
   this.name = name;
   this.age = age;
   this.job = job;
   this.printName = function(){
      console.log(this.name);
   }
}

let person1 = new Person('hua',10,'student');
let person2 = new Person('ke',20,'teacher');

3) 构造器模式+原型模式

定义一筐球,自己设计数据结构,然后能实现不同业务功能

思路:应该是用构造函数+原型链的模式实现封装,在构造函数中定义对象的属性,在函数原型对象 上定义方法

function Balls(){
    this.type = ['red','white','red','white'];
    this.weight = [10,20,30,40];
}

//在原型上定义方法,即要实现的业务功能
//找出红球
Balls.prototype.findRedBalls = function(){
  let type = this.type;
  let result = [];
  type.forEach((val)=>{
      if( val==='red' ){
          result.push(val);
      }
  });
}

//找出重量大于10的红球
Balls.prototype.findRedAndLarge10Balls = function(){
     let type = this.type;
     let weight = this.weight;
     let result = [];
     type.forEach((val,key)=>{
         if( val === 'red' && weight[key]>10  ){
             result.push(val);
         }
     });
}

//可以继续往原型上添加方法,即添加业务功能

- 代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理

 
- 适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。 

- 模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。 

1、单例模式

单例模式的定义

1)单例模式是一种常用的软件设计模式

2)保证一个类只有一个实例,并提供全局访问

通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个单例模式是最好的解决方案

实现原理----用一个变量标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象;否则创建该类的实例对象,并且将该实例对象用该标志保存

例子:用代理方式创建一个单例模式:给Body添加一个DIV

CreateDiv是一个简单的类,结合ProxySingleton达到单例模式的效果

const CreateDiv = function(html){
  this.html = html;
  this.init();
}

CreateDiv.prototype.init = function(){
  let div = document.createElement('div');
  div.innerHTML = this.html;
  document.body.appendChild(div);
}

const ProxySingleton = (function(html){
  let obj=null;
  return function(){
    if(obj){
      return obj;
    }
    return obj = new CreateDiv(html);
  }
})();

let obj1 = new ProxySingleton('div1');
let obj2 = new ProxySingleton('div2');
console.log(obj1===obj2);  //true

简单理解单例模式

1)单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等

2) 单例的缺点就是不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态

用单例模式,就是在适用其优点的状态下使用

单例模式的优点

1)实例控制 --------单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例

2)灵活性------------因为类控制了实例化过程,所以类可以灵活更改实例化过程

单例模式的缺点

1)开销----- 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。

2)可能的开发混淆--------使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

3)对象生存期-----不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用

惰性单例(P67)------在需要的时候才创建对象实例,对象实例的创建是通过立即执行函数(用一变量标志是否创建过对象)+闭包(函数返回值为函数)

管理单例的逻辑不变,可以抽象出来:用一个变量标志是否创建过对象,是,则下次直接返回这个已经创建好的对象,否则将创建的对象保存在该标志里面

// getSingle管理单例的职责
//fn用于创建对象的方法
const getSingle = function(fn){
    let result;
    return function(){
        return result || ( result = fn.apply(this,arguments) );
    }
}

//具体的fn,创建对象
const createLoginLayer = function(){
    let div = document.createElement('div');
    div.innerHTML = 'login layer';
    div.style.display = 'none'; //将登陆窗隐藏
    document.body.appendChild(div);
    return div;
}

let createSingleLoginLayer = getSingle( createLoginLayer );
// 真正创建的时机,即等到需要的时候在创建
document.getElementById('loginBtn').onclick = function(){
    let loginLayer = createSingleLoginLayer();
    loginLayer.style.display = 'block';
}

用单例模式实现功能----页面渲染完成一个列表后,给这个列表绑定click事件,如果通过ajax动态往列表追加数据,在使用事件代理的前提下,click事件实际上只需要在第一次渲染列表的时候被绑定一次,但我们不想判断是否是第一次渲染列表,可以利用单例模式,即将管理单例的函数和负责具体功能的函数进行组合

let getSingle = function(fn){
  let result;
  return function(){
    result || (result = fn.apply(this,arguments));
  }
};

let bindEvent = getSingle( function(){
  document.getELementById('div1').onclick = function(){
    console.log('click');
  };
  return true;
} );

let render = function(){
  console.log('开始渲染');
  bindEvent();
};

render();//div只被绑定一次
render();
render();

2、策略模式

定义:定义一序列的算法,把他们封装起来,并使它们可以相互替换。目的就是将算法的使用与算法的实现分离开来(将不变的部分和变化的部分分隔开)

在js中的策略模式:函数形式的策略对象

策略模式的程序至少有两部分:策略类(在一个对象(函数)里面封装具体的算法,也就是业务功能);环境类接受客户的请求,随后把请求委托给某一个策略类---即定义一个函数,在里面根据具体的参数来执行策略类里面具体的策略)

在js里面,通过将不同的策略(函数---实现相应的业务功能)封装在一个对象里面;然后在具体的环境下,通过传进来的参数,调用具体的策略

使用策略模式计算奖金------年终奖 = 基本工资 * 绩效等级

js版本的策略模式:将不同的策略定义成函数,把所有的策略定义在一个对象里面,然后在定义环境类

发出计算奖金(即调用calBonus())的命令,会根据传入的参数不同,计算得到不同的结果,体现了多态

// 根据不同的等级来计算年终奖,年终奖 = 基本工资 * 等级数
// 将所有的策略封装在一个对象里面
let strategies = {
    S(salary){
        return salary*4;
    },
    A(salary){
        return salary*3;
    },
    B(salary){
        return salary*2;
    }
}

//定义一个函数充当环境类,来接受用户的请求
let calBonus = function(level,salary){
    return strategies[level](salary);
}
console.log(calBonus('S',15000));

缓动动画和表单验证体现了多态(同一个命令可以有不同的行为)

用原生js实现动画效果的原理:通过连续改变元素的某个css属性,如left   right   bottom  color   background-color等属性来实现,连续改变是通过setInterval   setrTimeout等实现,在里面会有进行结束条件的判断

根据不同的缓动算法,让小球动起来

首先,用一个对象封装不同的缓动算法

然后在定义一个动画类,即构造函数,里面定义一些设置动画时用到的属性

在动画类原型上定义一个启动动画的函数,定义一些初始化的参数,并且调用缓动函数

在动画类原型上定义缓动函数,即每一帧的动画,也就是要修改的css的属性

// 让小球按照不同的缓动算法运动起来
// 将策略模式封装在一个对象里面
//策略类的参数依次为:已经消耗的时间,小球原始位置,目标位置,动画持续时间
const tween = {
  linear(t,b,c,d){
    return c*t/d+b;
  },
  easeIn(t,b,c,d){
    return c*(t/=d)*t+b;
  },
  sineaseIn(t,b,c,d){
    return c*(t/=d)*t*t+b;
  }
};

// 定义动画类,就是一个构造函数,里面定义一些动画需要的属性
let Animate = function(dom){
  this.dom = dom;          //运动的dom节点
  this.startTime = 0;     
  this.duration = null;
  this.startPos = 0;
  this.endPos = 0;
  this.propertyName = null; //dom节点需要改变的css属性名
  this.easing = null;       //缓动算法
}

//启动动画,就是初始化一些属性,然后调用缓动函数(定义每帧的动画)
Animate.prototype.start = function(propertyName,endPos,duration,easing){
  this.startTime = +new Date; //动画启动时间
  this.startPos = this.dom.getBoundingClientRect()[propertyName];
  this.propertyName = propertyName;
  this.endPos = endPos;
  this.duration = duration;
  this.easing = tween[easing];

  let self = this;
  let timeId = setInterval(function(){
    if( self.step() === false ){
      clearInterval( timeId );
    }
  },20);
}

// 缓动
Animate.prototype.step = function(){
  let t = +new Date; //取得当前时间
  if( t >= this.startTime + this.duration ){
    this.update(this.endPos); //更新小球的css属性
    return false;  //用于清除定时器,取消定时功能
  }
  let pos = this.easing( t-this.startTime,this.startPos,this.endPos-this.startPos,this.duration );
  //pos为小球当前的位置
  this.update(pos);//更新小球的css属性
}

// 更新小球的css属性
Animate.prototype.update = function(pos){
  this.dom.style[this.propertyName] = pos + 'px';
}

// 测试用例
let div = document.getElemenetById('div1');
let animate = new Animate(div);
animate.start('left',500,1000,'easeIn');

使用策略模式实现表单验证,用户名不能为空,密码长度不能少于6位,手机号码必须符合格式

// 用户名不能为空,密码长度不能少于6位,手机号码必须符合格式
// 将校验逻辑封装在策略对象里面
let strategies = {
  // 用户名不能为空
  isNonEmpty(value,errorMsg){
    if( value==='' ){
      return errorMsg;
    }
  },
  // 密码长度不能少于6位
  minLength( value,length,errorMsg ){
    if( value.length < length ){
      return errorMsg;
    }
  },
  // 手机号码必须符合格式
  isMobile( value,errorMsg ){
    if( !/(^1[3|5|8][0-9]{9}$)/.test(value) ){
      return errorMsg;
    }
  }
};

// 将Validator类作为Context,负责接收用户的请求并委托给具体的strategy
let Validator = function(){
  this.cache = [];//保存校验规则
}

Validator.prototype.add = function( dom,rules ){
  let self = this;
  for( let i=0,rule;rule=rules[i++]; ){
    (function(rule){
      let strategyArr = rule.strategy.split(':');
      let errorMsg = rule.errorMsg;
      self.cache.push(function(){
        let strategy = strategyArr.shift();
        strategyArr.unshift(dom.value);
        strategyArr.push(errorMsg);
        return strategies[strategy].apply(dom,strategyArr);
      });
    })(rule);
  }
};

Validator.prototype.start = function(){
  // 将保存在this.cache中所有校验规则取出来,执行
  for( let i=0,validatorFunc; validatorFunc=this.cache[i++]; ){
    let msg = validatorFunc(); //开始校验,并取得校验后的返回值
    if(msg){ //有返回值,证明校验没有通过
      return msg;
    }
  }
}

let registerForm = document.getElementById('registerForm');

let validateFunc = function(){
  let validator = new Validator();
  // 添加规则
  validator.add(registerForm.userName,[{strategy:'isNonEmpty',errorMsg:'用户名不能为空'},{strategy:'minLength:10',errorMsg:'用户名长度不能小于10'}]);
  validator.add(registerForm.passWord,[{'minLength:6','密码长度不能少于6位'}]);
  validator.add(registerForm.phoneNumber,[{'isMobile','手机号码不正确'}]);
  // 获得校验结果
  let errorMsg = validator.start();
  return errorMsg;
}

// 提交表单前验证
registerForm.onsubmit = function(){
  let errorMsg = validateFunc();
  if(errorMsg){
    console.log(errorMsg);
    // 阻止表单提交
    return false;
  }
}

3、代理模式---为一个对象提供一个代用品或占位符,以便控制对它的访问

- 代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理

保护代理------过滤掉一些不符合条件的请求,即控制不同权限的对象对目标对象的访问(在js中不易实现)

虚拟代理-----把一些开销很大的对象,延迟到真正需要的时候在创建(在js中常用)。比如虚拟代理实现图片预加载

缓存代理-----为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前的一样,则可以直接返回前面存储的缓存结果

例子:虚拟代理实现图片的预加载-----直接给img设置src属性,由于图片过大或者网络不佳,图片位置往往会是一片空白。而预加载则是先用一张loading占位,用异步方式加载图片,等图片加载完成在填充到img节点

好处:符合单一职责原则---给img设置src放在myImage中,图片预加载放在proxyImage中

const myImage = (functon(){
  let imgNode = document.createElement('img');
  document.body.appendChild(imgNode);
  return {
    setSrc(src){
      imgNode.src = src;
    }
  }
})();

const proxyImage = (function(){
  let img = new Image;
  // 异步事件
  img.onload = function(){
    myImage.setSrc(this.src)
  }
  return {
    setSrc(src){
      myImage.setSrc('c:/user/loading.gif');
      img.src = src;
    }
  }
})()

proxyImage.setSrc('http://imgcache.qq.com/music/one.jpg');

缓存代理:

const mult = (...args) => args.reduce( (ret,val)=>ret*val, 1 );
const proxyMult = (function(){
  let ret={};
  return function(...args){
    let flag = args.join(',')
    if( flag in ret ){
      return ret[flag];
    }
    return ret[flag] = mult.apply(this,args)
  }
})();
console.log(proxyMult(1,2,3))
console.log(proxyMult(1,2,3))

4、迭代器模式

定义:提供一种方法,顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

作用:将迭代过程业务逻辑分离出来,在使用迭代器模式之后,即使不关心对象的内部构造也可以按照顺序访问其中的每个元素

像数组中的:forEach  map   fiter  reduce----内部迭代器,将迭代过程封装在函数里面了,只需要一次初始化

Generator------外部迭代器需要显示的请求迭代下一个元素

迭代器分为:内部迭代器和外部迭代器,倒序迭代器,中止迭代器,

//内部迭代器
// let each = function(arr,fn){
//   for( let i=0,len=arr.length;i<len;i++ ){
//     fn.call(arr[i],arr[i],i);
//   }
// };

// each([12,34],function(value,key){
//     console.log(value,key);
// });

// 终止迭代器,就是如果回调函数的执行结果返回false则提前终止循环
let each = function(arr,fn){
  for( let i=0,len=arr.length;i<len;i++ ){
    if( fn(arr[i],i) === false ){
      break;
    }
  }
}
each([1,2,3,4,5],(value)=>{
  if(value>3){
    return false;
  }
  console.log(value);
});

5、发布-订阅模式(观察者模式)

定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在js中,一般用事件模型来替代传统的发布-订阅模式

// 将发布---订阅的功能提取出来
let event = {
  // 缓存列表,存放订阅者订阅的事件和其回调函数
  clientList:{},
  // 增加订阅者订阅的事件和回调函数
  listen(key,fn){
    if(!this.clientList[key]){
        //如果没有订阅过此类消息,则给该类消息创建一个缓存列表 
        this.clientList[key] = [];
    }
    // 将订阅消息添加进消息缓存列表
    this.clientList[key].push(fn);
  },
  // 发布消息
  on(...args){
    let key = args.shift(),
        fns = this.clientList[key];
    if( !fns || fns.length === 0 ){ //没有绑定对应消息
        return false;
    }

    for(let fn of fns){  //通知订阅该消息的所有订阅者
        fn.call(this,args); //args是trigger时带上的参数
    }

  },
  // 取消订阅
  off(key,fn){
      let fns = this.clientList[key];
      if(!fns){
          return false;  //key对应的消息没有被人订阅,直接返回
      }
      if(!fn){ //没有传入fn,则表示取消key对应消息的所有订阅
          fns && ( fns.length=0 )
      }else{
        let index = fns.findIndex(function(val){
            return val===fn;
        });
        fns.splice(index,1); //删除订阅者的回调函数
        console.log('success');
      }
  }
};

// 给所有对象都动态安装订阅-分布功能
let installEvent = obj=>{
  for( let key in event ){
      obj[key] = event[key];
  }
};

let salesOffice = {};
installEvent(salesOffice);

salesOffice.listen('square88',fn1=price=>console.log('price='+price));
salesOffice.listen('square88',fn2=price=>console.log('price='+price));
salesOffice.on('square88',8000);
salesOffice.off('square88',fn1);
salesOffice.on('square88',8000);
// 全局发布--订阅对象
let Event = {
  // 缓存列表,存放订阅者的回调函数
  clientList:{},
  // 增加订阅者--监听
  listen(key,fn){
    if(!this.clientList[key]){
        //如果没有订阅过此类消息,则给该类消息创建一个缓存列表 
        this.clientList[key] = [];
    }
    // 将订阅消息添加进消息缓存列表
    this.clientList[key].push(fn);
  },
  // 发布消息
  trigger(...args){
    let key = args.shift(),
        fns = this.clientList[key];
    if( !fns || fns.length === 0 ){ //没有绑定对应消息
        return false;
    }

    for(let fn of fns){  //通知订阅该消息的所有订阅者
        fn.call(this,args); //args是trigger时带上的参数
    }

  },
  // 取消订阅
  remove(key,fn){
      let fns = this.clientList[key];
      if(!fns){
          return false;  //key对应的消息没有被人订阅,直接返回
      }
      if(!fn){ //没有传入fn,则表示取消key对应消息的所有订阅
          fns && ( fns.length=0 )
      }else{
        let index = fns.findIndex(function(val){
            return val===fn;
        });
        fns.splice(index,1); //删除订阅者的回调函数
        console.log('success');
      }
  }
};

Event.listen('88',price=>console.log('price='+price));
Event.trigger('88',88888);

// 模块间通信,每次点击a模块里面的按钮,b模块显示按钮点击次数
// 将b订阅的事件添加到全局发布--订阅对象
let b=(()=>{
  let div = document.getElementById('show');
  Event.listen('add',count => div.innerHTML=count );
})()
// 通过a触发全局发布--订阅对象发布消息
let a = (()=>{
  let count = 0;
  let button = document.getElementById('count');
  button.addEventListener('click',()=>Event.trigger('add',count++))
})()

上面利用全局发布--订阅对象实现模块间通信

6、命令模式

命令模式中的命令指的是执行某些特定事情的指令

应用场景:需要向某些对象发送请求,但不知道接受者是谁;也不知道被请求的操作是什么

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="jquery-3.1.1.min.js"></script>
    </head>
    <body>
      <div id='ball' style="position:relative;background:#000;width:50px;height:50px"></div>
      输入小球移动后的位置:<input id='pos'>
      <button id='moveBtn'>start move</button>
      <button id='cancelBtn'>cancel move</button>
      <script>
        let ball = document.getElementById('ball'),
            pos = document.getElementById('pos'),
            moveBtn = document.getElementById('moveBtn'),
            cancelBtn = document.getElementById('cancelBtn'),
            MoveCommand ,
            moveCommand; 
        // 让小球按照不同的缓动算法运动起来
        // 将策略模式封装在一个对象里面
        //策略类的参数依次为:已经消耗的时间,小球原始位置,目标位置,动画持续时间
        const tween = {
          linear(t,b,c,d){
            return c*t/d+b;
          },
          easeIn(t,b,c,d){
            return c*(t/=d)*t+b;
          },
          sineaseIn(t,b,c,d){
            return c*(t/=d)*t*t+b;
          }
        };

        // 定义动画类,就是一个构造函数,里面定义一些动画需要的属性
        let Animate = function(dom){
          this.dom = dom;          //运动的dom节点
          this.startTime = 0;     
          this.duration = null;
          this.startPos = 0;
          this.endPos = 0;
          this.propertyName = null; //dom节点需要改变的css属性名
          this.easing = null;       //缓动算法
        }

        //启动动画,就是初始化一些属性,然后调用缓动函数(定义每帧的动画)
        Animate.prototype.start = function(propertyName,endPos,duration,easing){
          this.startTime = +new Date; //动画启动时间
          this.startPos = this.dom.getBoundingClientRect()[propertyName];
          this.propertyName = propertyName;
          this.endPos = endPos;
          this.duration = duration;
          this.easing = tween[easing];

          let self = this;
          let timeId = setInterval(function(){
            if( self.step() === false ){
              clearInterval( timeId );
            }
          },20);
        }

        // 缓动
        Animate.prototype.step = function(){
          let t = +new Date; //取得当前时间
          if( t >= this.startTime + this.duration ){
            this.update(this.endPos); //更新小球的css属性
            return false;  //用于清除定时器,取消定时功能
          }
          let pos = this.easing( t-this.startTime,this.startPos,this.endPos-this.startPos,this.duration );
          //pos为小球当前的位置
          this.update(pos);//更新小球的css属性
        }

        // 更新小球的css属性
        Animate.prototype.update = function(pos){
          this.dom.style[this.propertyName] = pos + 'px';
        }

        MoveCommand = function(receiver,pos){
          this.receiver = receiver;
          this.pos = pos;
          this.oldPos = null;
        };
        MoveCommand.prototype.execute = function(){
          this.receiver.start('left',this.pos,2000,'easeIn');
          this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName];
        };
        MoveCommand.prototype.undo = function(){
          this.receiver.start('left',this.oldPos,2000,'easeIn');
        };

        moveBtn.onclick = function(){
          let animate = new Animate(ball);
          moveCommand = new MoveCommand(animate,pos.value);
          moveCommand.execute();
        };
        cancelBtn.onclick = function(){
          moveCommand.undo();
        };
      </script>
    </body>
</html>
    

宏命令:是一组命令的集合,一次可以执行一批命令。宏命令是命令模式和组合模式的联用产物

子命令对象和宏命令对象要有同名的API属性

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="jquery-3.1.1.min.js"></script>
    </head>
    <body>
      <button id='btn'>execute command</button>
      <script>
          /***********定义宏命令对象*************/ 
          let MacroCommand = function(name){
            // 存放不同命令的容器
            this.commandList = [];
            this.name = name;
          };
          // 往容器添加不同的命令,用于组合不同的命令对象
          MacroCommand.prototype.add = function(command){
            this.commandList.push(command);
          };
          // 执行容器里面的所有的命令对象,也就是组合对象
          // 组合对象和基本对象同名的API
          MacroCommand.prototype.execute = function(){
            console.log('执行宏命令对象:'+this.name);
            for(let i=0,command;i<this.commandList.length;i++){
              command = this.commandList[i];
              command.execute();
            }
          };

          /*************定义基本对象************/ 
          let ChildCommand = function(name){
            this.name = name;
          };
          // 基本对象同名的API
          ChildCommand.prototype.execute = function(){
            console.log('执行基本命令:'+this.name);
          };
          // 防止误操作,基本对象不能在有子对象
          ChildCommand.prototype.add = function(){
            console.log('基本对象不能再有子对象');
            throw new Error('基本对象不能再有子对象');
          };

          /*************组合基本对象,构成宏命令************/ 
          let macroCommand = new MacroCommand('宏对象1'),
              childCommand1 = new ChildCommand('关门'),
              childCommand2 = new ChildCommand('关电脑'),
              childCommand3 = new ChildCommand('开电视');
          macroCommand.add(childCommand1);
          macroCommand.add(childCommand2);
          macroCommand.add(childCommand3);
          /*************给按钮绑定宏命令**************/ 
          document.getElementById('btn').onclick = function(){
            macroCommand.execute();
          };
      </script>
    </body>
</html>
    

7、组合模式----树形结构

定义:就是用小的子对象来构建更大的对象,而这些小的子对象本身也也许是由更小的对象构成

特点:可以用树形方式创建对象的结构;把相同的操作应用在组合对象和单个对象上(相同的API)

使用场景

1)表示对象的部分-整体层次结构。只需要通过请求树中的最顶层对象,便能对树做统一的操作

2)客户希望统一对待树中的所有对象。忽略组合对象和叶对象的区别,他们会各自做自己的事情

文件扫描,适合使用组合模式,路径是树形结构

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="jquery-3.1.1.min.js"></script>
    </head>
    <body>
      <button id='btn'>execute command</button>
      <script>
          /***********定义宏命令对象*************/ 
          let MacroCommand = function(name){
            // 存放不同命令的容器
            this.commandList = [];
            this.name = name;
            this.parent = null;
          };
          // 往容器添加不同的命令,用于组合不同的命令对象
          MacroCommand.prototype.add = function(command){
            this.commandList.push(command);
            command.parent = this;
          };
          // 执行容器里面的所有的命令对象,也就是组合对象
          // 组合对象和基本对象同名的API
          MacroCommand.prototype.execute = function(){
            console.log('组合对象:'+this.name);
            for(let i=0,command;i<this.commandList.length;i++){
              command = this.commandList[i];
              command.execute();
            }
          };
          // 移除某个子命令,相当于在上级删除该命令
          MacroCommand.prototype.remove = function(){
            if(!this.parent){ //根节点或者游离节点
              return;
            }
            for( let commands=this.parent.commandList,len=commands.length-1,command;len>=0;len-- ){
              command = commands[len];
              if(command === this){
                console.log('移除组合对象:'+this.name);
                commands.splice(len,1);
              }
            }
          };

          /*************定义基本对象************/ 
          let ChildCommand = function(name){
            this.name = name;
            this.parent = null;
          };
          // 基本对象同名的API
          ChildCommand.prototype.execute = function(){
            console.log('基本命令:'+this.name);
          };
          // 防止误操作,基本对象不能在有子对象
          ChildCommand.prototype.add = function(){
            console.log('基本对象不能再有子对象');
            throw new Error('基本对象不能再有子对象');
          };
          // 移除操作
          ChildCommand.prototype.remove = function(){
            if(!this.parent){
              return;
            }
            for( let commands=this.parent.commandList,len=commands.length-1,command;len>=0;len-- ){
              command = commands[len];
              if(command === this){
                console.log('移除基本对象:'+this.name);
                commands.splice(len,1);
              }
            }

          };

          /*************组合基本对象************/ 
          let childCommand1 = new ChildCommand('关门'),
              childCommand2 = new ChildCommand('关电脑'),
              childCommand3 = new ChildCommand('开电视'),
              macroCommand = new MacroCommand('组合对象'),
              macroCommand1 = new MacroCommand('组合对象1');
          /*********组合**********/ 
          macroCommand1.add(childCommand1);
          macroCommand1.add(childCommand2);
          /*********组合**********/ 
          macroCommand.add(childCommand3);
          macroCommand.add(macroCommand1);
          /*************给按钮绑定组合对象**************/ 
          document.getElementById('btn').onclick = function(){
            macroCommand1.remove();
            macroCommand.execute();
          };
      </script>
    </body>
</html>
    

8、模板方法模式

定义:只需使用原型链继承就可以实现简单的模式

组成:由抽象的父类具体实现的子类组成。抽象父类封装子类的算法框架,公共方法和封装子类中所有方法的执行顺序方法。子类通过继承抽象类,可以重写父类方法

应用场景:模板方法模式常用于搭建项目的框架,定好框架的骨架,然后继承框架的结构之后,往里面添加功能;

web开发中,搭建一系列的UI组件,组件的构建过程:

1)初始化一个div容器

2)通过ajax请求拉取到相应的数据

3)把数据渲染到div容器里面,完成组件构造

4)通知用户组件渲染完毕

1)、4)相同,子类只需要重写2)、3)

模板方法模式中的钩子方法:用于解决一些特例,隔离变化。在父类中容易发生变化的地方放置钩子,钩子有一个默认的实现,由子类决定要不要挂钩。钩子方法的返回结果决定模板方法后面的执行步骤

茶与咖啡例子

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="jquery-3.1.1.min.js"></script>
    </head>
    <body>
      <button id='btn'>execute command</button>
      <script>
          /**********抽象父类,泡一杯饮料的整个过程*********/ 
          let Beverage = function(){};
          //下面3个空方法,由子类重写,父类提供接口 
          Beverage.prototype.boilWater = function(){
            console.log('把水煮沸');
          };
          // 防止子类没有重写这些方法带来的错误
          Beverage.prototype.brew = function(){
            throw new Error('子类必须重写brew');
          };
          Beverage.prototype.pourInCup = function(){
            throw new Error('子类必须重写pourInCup');
          };
          Beverage.prototype.addCondiments = function(){
            throw new Error('子类必须重写addCondiments');
          };
          // 钩子方法,true-表示挂钩,false-表示不挂钩,默认为true
          Beverage.prototype.customerWantsCondiments = function(){
            return true;
          };
          //这个就是模板方法,封装子类的算法框架,指导子类以何种顺序执行哪些方法
          Beverage.prototype.init = function(){
            this.boilWater();
            this.brew();
            this.pourInCup();
            // 这里需要判断钩子方法的返回值,做下一步的处理
            if( this.customerWantsCondiments() ){ //挂钩返回true,表示加饮料
              this.addCondiments();
            } 
          };

          /*******创建子类Coffee,继承父类,并且重写父类中的方法********/ 
          let CoffeeWithHook = function(){};
          // 通过原型链继承父类
          CoffeeWithHook.prototype = new Beverage();
          // 重写父类中的方法
          CoffeeWithHook.prototype.brew = function(){
            console.log('用沸水冲泡咖啡');
          };
          CoffeeWithHook.prototype.pourInCup = function(){
            console.log('把咖啡倒进杯子');
          };
          CoffeeWithHook.prototype.addCondiments = function(){
            console.log('加糖和牛奶');
          };
          // 重写父类中的钩子函数
          CoffeeWithHook.prototype.customerWantsCondiments = function(){
            return window.confirm('咖啡中需要添加饮料吗?');
          };

          /*********创建子类Tea,继承父类,并且重写父类中的方法************/ 
          let TeaWithHook = function(){};
          // 通过原型链继承父类
          TeaWithHook.prototype = new Beverage();
          // 重写父类中的方法
          TeaWithHook.prototype.brew = function(){
            console.log('用沸水浸泡茶叶');
          };
          TeaWithHook.prototype.pourInCup = function(){
            console.log('把茶倒进杯子');
          };
          TeaWithHook.prototype.addCondiments = function(){
            console.log('加柠檬');
          };
           // 重写父类中的钩子函数
          TeaWithHook.prototype.customerWantsCondiments = function(){
            return window.confirm('茶中需要添加饮料吗?');
          };
          /******创建Tea和Coffee实例对象********/ 
          let coffee = new CoffeeWithHook(),
              tea = new TeaWithHook();
          coffee.init();
          tea.init();

      </script>
    </body>
</html>
    

9、职责链模式

使多个对象都有机会处理请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止,避免请求的发送者和处理者之间的耦合关系。

链中的节点可以灵活拆分和重组

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="jquery-3.1.1.min.js"></script>
    </head>
    <body>
      <button id='btn'>execute command</button>
      <script>
        // orderType---订单类型,1-500元定金用户,2-200元定金用户,3-普通购买用户
        // pay----是否已经支付定金,true-已经支付,false相反
        // stock---当前用于普通购买的手机库存数量,已支付金额的用户不受此影响
        /******购买模式的节点函数,不能处理请求返回特定字符串需要往后面传递******/ 
        let order500 = function(orderType,pay,stock){
          if(orderType===1 && pay===true){
            console.log('500元定金预购,得到100元优惠券');
          }else{
            return 'nextSuccessor'; //把请求往后传
          }
        };
        let order200 = function(orderType,pay,stock){
          if(orderType===2 && pay===true){
            console.log('200元定金预购,得到50元优惠券');
          }else{
            return 'nextSuccessor'; //把请求往后传
          }
        };
        let orderNormal = function(orderType,pay,stock){
          if(stock>0){
            console.log('普通购买,无优惠券');
          }else{
            console.log('手机库存不足');
          }
        };

        /******把函数包装进职责链节点*****/ 
        let Chain = function(fn){
          this.fn = fn;  //需要被包装的函数
          this.successor = null; //在链中的下一个节点
        };
        // 指定在链中的下一个节点
        Chain.prototype.setNextSuccessor = function(successor){
          return this.successor = successor;
        };
        // 传递请求给某个节点
        Chain.prototype.passRequest = function(){
          let ret = this.fn.apply(this,arguments);
          if(ret === 'nextSuccessor'){
            return this.successor &&  this.successor.passRequest.apply(this.successor,arguments);
          }
          return ret;
        };

        /******把3个函数包装职责链中的节点******/ 
        let chainOrder500 = new Chain(order500),
            chainOrder200 = new Chain(order200),
            chainOrderNormal = new Chain(orderNormal);
        /*******指定节点在职责链中的顺序******/ 
        chainOrder500.setNextSuccessor(chainOrder200);
        chainOrder200.setNextSuccessor(chainOrderNormal);
        /*******把请求传递给第一个节点******/ 
        chainOrder500.passRequest(1,true,500);//500元定金预购,得到100元优惠券
        chainOrder500.passRequest(2,true,500);//200元定金预购,得到50元优惠券
        chainOrder500.passRequest(3,true,500);//普通购买,无优惠券
        chainOrder500.passRequest(1,false,0);//手机库存不足
      </script>
    </body>
</html>
    

10、中介者模式

解除对象与对象之间的耦合关系,使网状的多对多关系变成简单的一对多关系。将逻辑判断都放在中介者对象里面,触发者只需要向中介者传递本身,所有的节点对象只跟中介者通信

将所有的逻辑判断都写在中介者里面,然后在外面,只需要对对象书写监听事件,在监听事件里面调用中介者提供的API

购买商品

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="jquery-3.1.1.min.js"></script>
    </head>
    <body>
      <div>
        选择颜色:<select id='colorSelect'>
                    <option value=''>请选择</option>
                    <option value='red'>red</option>
                    <option value='blue'>blue</option>
                  </select>
        选择内存:<select id='memorySelect'>
                    <option value=''>请选择</option>
                    <option value='32g'>32g</option>
                    <option value='64g'>64g</option>
                  </select>
        输入购买数量:<input type="text" id='numberInput'><br/><br/>
        已选择颜色:<div id='colorInfo'></div><br/>
        已选择内存:<div id='memoryInfo'></div><br/>
        输入的数量:<div id='numberInfo'></div><br/>
        <button id='nextBtn' disabled="true">请选择手机颜色、内存和购买数量</button>
      </div>
      <script>
        //数据 
        let goods = {
          'red|32g':3,
          'red|16g':0,
          'blue|32g':1,
          'blue|16g':6
        }; 

        let colorSelect = document.getElementById('colorSelect'),
            memorySelect = document.getElementById('memorySelect'),
            numberInput = document.getElementById('numberInput');
        //中介者,将所有的逻辑判断都写在该中介者里面
        let mediator = (function(){
          let colorInfo = document.getElementById('colorInfo'),
              memoryInfo = document.getElementById('memoryInfo'),
              numberInfo = document.getElementById('numberInfo'),
              nextBtn = document.getElementById('nextBtn');
          return {
            changed:function(obj){
              let color = colorSelect.value,
                  memory = memorySelect.value,
                  number = numberInput.value,
                  stock = goods[color + '|' + memory];//库存量

              //判断发生变化的对象
              if( obj === colorSelect ){
                colorInfo.innerHTML = color;
              }else if( obj === memorySelect ){
                memoryInfo.innerHTML = memory;
              }else if( obj === numberInput ){
                numberInfo.innerHTML = number;
              }

              // 对一些边界条件进行判断
              if(!color){
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请选择手机颜色';
                return;
              }
              if( !memory ){
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请选择内存大小';
                return;
              }
              if( !Number.isInteger(number-0) || number<=0 ){
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请输入正确的数量';
                return;
              }

              nextBtn.disabled = false;
              nextBtn.innerHTML = '加入购物车';
            }
          }
        })();

        //添加事件监听,并且调用中介者中的函数
        colorSelect.onchange = function(){
          mediator.changed(this);
        };

        memorySelect.onchange = function(){
          mediator.changed(this);
        };

        numberInput.oninput = function(){
          mediator.changed(this);
        };

      </script>
    </body>
</html>
    

11、装饰者模式

可以动态的给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象

在不改变源码的情况下,能给函数添加功能,符合开放-封闭原则

通过保存原引用就可以改写某个函数

原理:利用闭包规定对原函数和新添加功能函数的执行顺序

///1、fn---原函数,beforefn----新添加的功能函数
// 利用闭包实现对原函数和新添加功能的函数的执行顺序的规定
let before = function(fn,beforefn){
  return function(){
    beforefn.apply(this,arguments);
    fn.apply(this,arguments);
  }
};

let a = before(function(){console.log(1)},function(){console.log(2)});
a = before(a,function(){console.log(3)});
a();

// 2、
let after = function(fn,afterfn){
  return function(){
    fn.apply(this,arguments);
    afterfn.apply(this,arguments);
  }
};

let b = after(function(){console.log(1)},function(){console.log(2)});
b = after(b,function(){console.log(3)});
b();

例子:打开登录浮层后上报数据

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="jquery-3.1.1.min.js"></script>
    </head>
    <body>
      <button id='button' tag='login'></button>
      <script>
        //1、fn---原函数,beforefn----新添加的功能函数
        // 利用闭包实现对原函数和新添加功能的函数的执行顺序的规定
        Function.prototype.before = function(beforefn){
          let _self = this;
          return function(){
            beforefn.apply(this,arguments);
            return _self.apply(this,arguments);
          }
        };

        // 2、
        Function.prototype.after = function(afterfn){
          let _self = this;
          return function(){
            let ret = _self.apply(this,arguments);
            afterfn.apply(this,arguments);
            return ret;
          }
        };

        let showLogin = function(){
          console.log('打开登录浮层');
        };
        let log = function(){
          console.log('上报标签为:'+this.getAttribute('tag'));
        };

        // 将两个功能的函数进行合并
        showLogin = showLogin.after(log);//打开登录浮层后,上报数据
        document.getElementById('button').onclick = showLogin;

      </script>
    </body>
</html>
    

用AOP的方式动态改变函数的参数

给ajax请求中的数据添加Token字段,而不用改变原来的ajax方法,使得ajax在别处可以使用

///1、fn---原函数,beforefn----新添加的功能函数
// 利用闭包实现对原函数和新添加功能的函数的执行顺序的规定
Function.prototype.before = function(beforefn){
  let _self = this;
  return function(){
    beforefn.apply(this,arguments);
    return _self.apply(this,arguments);
  }
};

// 干净的ajax
let ajax = function(type,url,param){
  console.log(param); //发送ajax请求代码略
};
// 产生Token字段的函数
let getToken = function(){
  return 'Token';
};
// 通过before()为ajax的参数添加Token
let newAjax = ajax.before(function(type,url,param){
  param.token = getToken();
});

newAjax('GET','http://xxx.com',{name:'jiang'});
// 输出结果
// { name: 'jiang', token: 'Token' }

AOP(面向切面的编程)主要是将一些与核心业务逻辑模块无关的功能抽离出来,这些功能通常包括日志统计,安全控制,或者是异常处理等等

插件式的表单验证

先验证表单数据的合法性,验证通过在提交

  <!doctype html>
  <html>
      <head>
          <meta charset="utf-8"/>
          <script src="jquery-3.1.1.min.js"></script>
      </head>
      <body>
        用户名:<input id='username' type='text'/>
        密码:<input type="password" id="password" />
        <input id='submitBtn' type='button' value='submit'/>
        <script>
          let username = document.getElementById('username'),
              password = document.getElementById('password'),
              submitBtn = document.getElementById('submitBtn');

          // 装饰者模式,如果beforefn的执行结果返回false,表示不再执行后面的原函数
          Function.prototype.before = function(beforefn){
            let _self = this;
            return function(){
              if( beforefn.apply(this.arguments) === false ){
                return;
              }
              return _self.apply(this,arguments);
            }
          };
          // 验证表单数据,作为before()函数的参数
          let validata = function(){
            if( username.value === '' ){
              console.log('username can not be void');
              return false;
            }
            if( password.value === '' ){
              console.log('password can not be void');
              return false;
            }
          };
          // 定义提交函数功能
          let formSubmit = function(){
            let param = {
              username:username.value,
              password:password.value
            };
            ajax('http://xxx.com/login',param);
          };
          // 将验证数据的合法性代码扩展到提交函数中
          formSubmit = formSubmit.before(validata);
          // 点击按钮,验证数据的合法性,通过则提交
          submitBtn.onclick = function(){
            formSubmit();
          };
        </script>
      </body>
  </html>
      

装饰者和代理模式的区别

共同点:

1)描述了为对象提供一定程度上的间接引用,实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求

12、状态模式

状态模式的关键:区分事物内部的状态,事物内部的状态往往会带来事物的行为改变。把事物的每种状态都封装成单独的类,跟这个状态有关的行为都被封装在这个类的内部,每个状态类有同名的API方法,并且需要持有总对象的引用;然后在定义一个总对象,在里面实力化每一个状态类

例子:有一种灯,按一下打开弱光,在按一下是强光,再按一下关闭电灯,如此循环反复

适合用状态模式:同一个元素,每次操作会带来不同的行为,则可以用状态模式实现

  <!doctype html>
  <html>
      <head>
          <meta charset="utf-8"/>
          <script src="jquery-3.1.1.min.js"></script>
      </head>
      <body>
        <script>
            // 状态类中有一些共同的API,那么为了保证每一个状态类都实现
            // 这个方法,就定义一个抽象父类的抽象方法,让其子类都实现该接口
            // 否则在程序运行期间就抛出错误
            /******状态类的抽象父类*******/
            let State = function(){};
            State.prototype.buttonWasPressed = function(){
              throw new Error('父类的buttonWasPressed必须重写');
            };
            /*****关闭电灯状态类*******/ 
            let OffLightState = function(light){
              // light是基本对象
              this.light = light;
            };
            // 通过原型链继承抽象类State
            OffLightState.prototype = new State();
            // 重写父类的抽象方法
            OffLightState.prototype.buttonWasPressed = function(){
              console.log('弱光');
              // 状态由关闭切换到弱光
              this.light.setState(this.light.weakLightState);
            };

            /*****弱光状态类*******/ 
            let WeakLightState = function(light){
              // light是总对象
              this.light = light;
            };
            // 通过原型链继承抽象类State
            WeakLightState.prototype = new State();
            WeakLightState.prototype.buttonWasPressed = function(){
              console.log('强光');
              // 状态由弱光切换到强光
              this.light.setState(this.light.strongLightState);
            };

            /*****强光状态类*******/ 
            let StrongLightState = function(light){
              // light是总对象
              this.light = light;
            };
            // 通过原型链继承抽象类State
            StrongLightState.prototype = new State();
            StrongLightState.prototype.buttonWasPressed = function(){
              console.log('关灯');
              // 状态由强光切换到关闭
              this.light.setState(this.light.offLightState);
            };

            /*****基本对象*******/ 
            let Light = function(){
              // 创建每个状态类的实例对象
              this.offLightState = new OffLightState(this);
              this.weakLightState = new WeakLightState(this);
              this.strongLightState = new StrongLightState(this);
              // 控制灯状态切换的按钮
              this.button = null;
              // 持有当前状态的状态类对象
              this.currState = null;
            };
            // 创建button对象,并且设置click事件,将请求委托给当前持有的状态对象执行
            Light.prototype.init = function(){
              // 创建button
              let button = document.createElement('button'),
                  self = this;
              this.button = document.body.appendChild(button);
              this.button.innerHTML = '开关';
              // 设置当前状态类
              this.currState = this.offLightState;
              this.button.onclick = function(){
                self.currState.buttonWasPressed();
              };
            };
            // 切换ligth对象的状态
            Light.prototype.setState = function(newState){
              this.currState = newState;
            };

            // 测试
            let light = new Light();
            light.init();
        </script>
      </body>
  </html>
      

文件上传,音乐,视频播放器

  <!doctype html>
  <html>
      <head>
          <meta charset="utf-8"/>
          <script src="jquery-3.1.1.min.js"></script>
      </head>
      <body>
        <script>
              /**************************************/
              // window.external.upload,在页面中模拟创建上传插件
              // 上传是一个异步的过程,控件不停的调用js提供的全局函数window.external.upload
              // 通知js目前的上传进度,控件会把当前文件的状态state作为该函数的参数
              // 只是模拟
              window.external.upload = function(state){
                // sign,uploading,pause,done,error
                console.log(state);
              };
              /**************************************/
              /***********<embed>创建上传插件对象*********/ 
              let plugin = (function(){
                // 创建插件对象
                let Plugin = document.createElement('embed');
                Plugin.style.display = 'none';
                Plugin.type = 'application/txftn-webkit';
                // 定义插件的方法
                Plugin.sign = function(){
                  console.log('开始扫描文件');
                };
                Plugin.uploading = function(){
                  console.log('开始上传文件');
                };
                Plugin.pause = function(){
                  console.log('暂停文件上传');
                };
                Plugin.done = function(){
                  console.log('文件上传成功');
                };
                Plugin.error = function(){
                  console.log('文件上传失败');
                };
                Plugin.del = function(){
                  console.log('删除文件成功');
                };
                // 将插件添加到DOM中并返回
                document.body.appendChild(Plugin);
                return Plugin;
              })();

              /**************************************/
              /***********文件上传状态抽象类*********/ 
              let State = function(){};
              // 按钮1点击事件,用于切换上传和暂停状态
              State.prototype.clickHandler1 = function(){
                throw new Error('子类必须重写clickHandler1');
              };
              // 按钮2点击事件,用于删除上传文件功能
              State.prototype.clickHandler2 = function(){
                throw new Error('子类必须重写clickHandler2');
              };
              /**************************************/
              /************扫描状态类***************/ 
              let SignState = function(uploadObj){
                // 对基本对象的持有
                this.uploadObj = uploadObj;
              };
              // 继承状态抽象类
              SignState.prototype = new State();
              // 重写抽象方法
              SignState.prototype.clickHandler1 = function(){
                console.log('扫描中,点击无效');
              };
              SignState.prototype.clickHandler2 = function(){
                console.log('文件正在扫描,不能删除');
              };
              /**************************************/
              /************上传状态类***************/ 
              let UploadingState = function(uploadObj){
                this.uploadObj = uploadObj;
              };
              // 继承状态抽象类
              UploadingState.prototype = new State();
              // 重写抽象方法
              UploadingState.prototype.clickHandler1 = function(){
                // 状态由上传切换到暂停
                this.uploadObj.pause();
              };
              UploadingState.prototype.clickHandler2 = function(){
                console.log('文件正在上传中,不能删除');
              };
              /**************************************/
              /************暂停状态类***************/ 
              let PauseState = function(uploadObj){
                this.uploadObj = uploadObj;
              };
              // 继承状态抽象类
              PauseState.prototype = new State();
              // 重写抽象方法
              PauseState.prototype.clickHandler1 = function(){
                // 状态由暂停切换到上传
                this.uploadObj.uploading();
              };
              PauseState.prototype.clickHandler2 = function(){
                // 删除文件
                this.uploadObj.del();
              };
              /**************************************/
              /************上传成功状态类***************/ 
              let DoneState = function(uploadObj){
                this.uploadObj = uploadObj;
              };
              // 继承状态抽象类
              DoneState.prototype = new State();
              // 重写抽象方法
              DoneState.prototype.clickHandler1 = function(){
                console.log('文件已经完成上传,点击无效');
              };
              DoneState.prototype.clickHandler2 = function(){
                // 删除文件
                this.uploadObj.del();
              };
              /******************************************/
              /************上传失败状态类***************/ 
              let ErrorState = function(uploadObj){
                this.uploadObj = uploadObj;
              };
              // 继承状态抽象类
              ErrorState.prototype = new State();
              // 重写抽象方法
              ErrorState.prototype.clickHandler1 = function(){
                console.log('文件上传失败,点击无效');
              };
              ErrorState.prototype.clickHandler2 = function(){
                // 删除文件
                this.uploadObj.del();
              };
              /******************************************/
              /************定义状态类基本对象***************/ 
              let Upload = function(fileName){
                // 在其构造函数中创建每一个状态类的实例对象
                this.signState = new SignState(this);
                this.uploadingState = new UploadingState(this);
                this.pauseState = new PauseState(this);
                this.doneState = new DoneState(this);
                this.errorState = new ErrorState(this);
                // 设置一些信息变量
                this.plugin = plugin;
                this.fileName = fileName;
                // 触发事件的源对象
                // 上传与暂停状态切换
                this.button1 = null;
                // 删除上传文件
                this.button2 = null;
                // 设置初始化的状态
                this.currState = this.signState;
                // 保留对创建的DOM的引用
                this.dom = null;
              };
              /***********************************************************/ 
              Upload.prototype.init = function(){
                let that = this;
                // 往页面中创建跟上传流程有关的DOM节点
                this.dom = document.createElement('div');
                this.dom.innerHTML = `<span>文件名称:${this.fileName}</span>
                                      <button data-action='button1'>扫描中</button>
                                      <button data-action='button2'>删除</button>`;
                document.body.appendChild(this.dom);
                // 绑定按钮事件
                this.button1 = this.dom.querySelector( '[data-action="button1"]' );
                this.button2 = this.dom.querySelector( '[data-action="button2"]' );
                this.bindEvent();
              };
              /***********************************************************/ 
              // 负责具体的按钮事件绑定,点解按钮,context不做任何操作,
              // 把请求委托给当前的状态类来执行
              Upload.prototype.bindEvent = function(){
                let self = this;
                this.button1.onclick = function(){
                  self.currState.clickHandler1();
                };
                this.button2.onclick = function(){
                  self.currState.clickHandler2();
                };
              };
              /***********************************************************/ 
              // 将状态对应的逻辑行为放在基本状态类Upload类中
              Upload.prototype.sign = function(){
                this.plugin.sign();
                this.currState = this.signState;
              };
              Upload.prototype.uploading = function(){
                this.button1.innerHTML = '正在上传,点击暂停';
                this.plugin.uploading();
                this.currState = this.uploadingState;
              };
              Upload.prototype.pause = function(){
                this.button1.innerHTML = '已经暂停,点击继续上传';
                this.plugin.pause();
                this.currState = this.pauseState;
              };
              Upload.prototype.done = function(){
                this.button1.innerHTML = '上传完成';
                this.plugin.done();
                this.currState = this.doneState;
              };
              Upload.prototype.error = function(){
                this.button1.innerHTML = '上传失败';
                this.plugin.done();
                this.currState = this.errorState;
              };
              Upload.prototype.del = function(){
                this.plugin.del();
                this.dom.parentNode.removeChild(this.dom);
              };
              /***********************************************************/ 
              /***********************************************************/ 
              // 测试
              let uploadObj = new Upload('设计模式');
              uploadObj.init();

              window.external.upload = function(state){
                uploadObj[state]();
              };
              // 开始扫描
              window.external.upload('sign');

              // 用setTimeout()来模拟异步上传的过程
              // 1秒后开始上传
              setTimeout(function(){
                window.external.upload('uploading');
              },8000);

              // 5秒后上传完成
              setTimeout(function(){
                window.external.upload('done');
              },15000);
        </script>
      </body>
  </html>
      

对于开灯关灯,可以使用js的事件委托和状态机实现,Function.prototype.call

// 使用js的状态机,通过委托技术
// Function.prototype.call把请求委托给某个字面量对象执行
// 状态机
let FSM = {
  off:{
    buttonWasPressed(){
      console.log('关灯');
      this.button.innerHTML = '开灯';
      this.currState = FSM.on;
    }
  },
  on:{
    buttonWasPressed(){
      console.log('关灯');
      this.button.innerHTML = '关灯';
      this.currState = FSM.off;
    }
  }
};
// 类
let Light = function(){
  // 设置当前状态
  this.currState = FSM.off;
  this.button = null;
};
Light.prototype.init = function(){
  let button = document.createElement('button'),
      self = this;
  button.innerHTML = '已经关灯';
  this.button = document.body.appendChild(button);
  this.button.onclick = function(){
    // 把请求委托给状态机
    self.currState.buttonWasPressed.call(self);
  };
};
// 测试
let light = new Light();
light.init();

12、适配器模式

作用:解决两个软件实体间的接口不兼容问题,别名包装器

例子:对于一个用于根据不同的地图,但是都是渲染地图的功能,但是每个地图的渲染的API不一样,这时候需要对地图的API做一个适当的包装,也就是封装成一个适配器类,即在这个对象里面有一个同名的API

let googleMap = {
	show(){
		console.log('开始渲染谷歌地图');
	}
};
let baiduMap = {
	display(){
		console.log('开始渲染百度地图');
	}
};
let baiduMapAdapter = {
	show(){
		return baiduMap.display();
	}
}
let renderMap = function(map){
	if( map.show instanceof Function ){
		map.show();
	}
};

// 测试
renderMap(googleMap); //开始渲染谷歌地图
renderMap(baiduMapAdapter); //开始渲染谷歌地图

数据格式有所变化,需要对旧的数据的格式进行转换 

// 广东省所有城市和id
let getGuangDongCity = function(){
	let guangdongCity = [
		{	name:'shenzhen',
			id:11
	    },
		{	name:'guangzhou',
			id:12
		}
	];
	return guangdongCity;
};
let render = function(fn){
	console.log('开始渲染广东省地图');
	document.write(JSON.stringify(fn()));
};
// render(getGuangDongCity);
// 新数据格式
// let guangdongCity = {
// 	shenzhen:11,
// 	guangzhou:12,
// 	zhuhai:13
// };

// 新增一个数据格式转换的适配器
let addressAdapter = fucntion(oldAddressFn){
	let address = {},
		oldAddress = oldAddressFn();
	for( let i=0,data;i<oldAddress.length;i++ ){
		data = oldAddress[i];
		address[data.name] = data.id;
	}

	return function(){
		return address;
	};
};
render( addressAdapter(getGuangDongCity) );

13、享元模式

是一种用于性能优化的模式,其核心运用共享技术来有效支持大量细粒度的对象

猜你喜欢

转载自blog.csdn.net/tangxiujiang/article/details/88718236