模板方法模式
- 父类定义一组操作算法骨架,而将一些实现步骤延迟到子类中,使得子类可以不改变父类的算法结构的同时可重新定义算法中某些实现步骤
- 核心在于对方法的重用,将核心方法封装在基类中,让子类继承基类的方法实现基类方法的共享达到方法共用
- 案例一:基于喝茶和喝咖啡抽象创建出模板父类(饮料类)
var Beverages=function(){};
Beverages.prototype.boilWater=function(){
console.log('把水煮沸')
}
Beverages.prototype.brew=function(){
throw new Error('空方法应该由子类重写')
};//空方法应该由子类重写
Beverages.prototype.pourInCup=function(){
throw new Error('空方法应该由子类重写')
};//空方法应该由子类重写
Beverages.prototype.init=function(){
this.boilWater();
this.brew();
this.pourInCup();
}
/**
* 创建coffee类
* 以父类Beverages为模板方法
*/
var Coffee=function(){};
Coffee.prototype=new Beverages();
Coffee.prototype.brew=function(){
console.log('用沸水冲泡咖啡')
}
Coffee.prototype.pourInCup=function(){
console.log('加糖')
}
var coffee=new Coffee();
coffee.init();
/***输出结果为:
* 把水煮沸
用沸水冲泡咖啡
加糖
*/
-
案例二:
-
数据格式是确定的
new CancelAlert(
{
title:'标题',
content:'内容',
success:function(){console.log('ok')},
fail:function(){console.log('cancel')}
}
).init();
- 带有取消按钮的弹出框
var CancelAlert=function(data){
//继承标题提示框构造函数
TitleAlert.call(this,data);
//取消按钮文案
this.cancel=data.cancel;
//创建取消按钮
this.cancelBtn=document.createElement('p');
//为取消按钮添加类
this.cancelBtn.className='cancel';
//设置取消按钮内容
this.cancelBtn.innerHTML=this.cancel || '取消'
}
- 继承标题提示框(基类)原型方法
CancelAlert.prototype=new Alert();
CancelAlert.prototype.init=function(){
//继承标题提示框创建方法
TitleAlert.prototype.init.call(this);
}
CancelAlert.prototype.bindEvent=function(){
var me=this;
//标题提示框绑定事件方法继承
TitleAlert.prototype.bindEvent.call(me);
//取消按钮绑定事件
this.cancelBtn.onclick=function(){
//执行取消回调函数
me.fail();
me.hide();
}
}
- 标题提示框(为上面的取消提示框的模板)
var TitleAlert=function(data){
//继承基本提示框构造函数---//不能继承到原型上的属性方法
Alert.call(this,data);
//设置标题内容
this.title=data.title;
//设置标题组件
this.titleNode=document.createElement('h2')
//标题组件中写入标题内容
this.titleNode.innerHTML=this.title;
}
- 继承基本提示框方法和原型拓展
TitleAlert.prototype=new Alert();
//对基本提示框创建方法拓展
TitleAlert.prototype.init=function(){
//插入标题---自己的新增方法
this.panel.insertBefore(this.titleNode,this.panel.firstChild)
//继承基本提示框init方法
Alert.prototype.init.call(this)
}
- 模板类 基础提示框 根据data渲染数据(为上面标题提示框的模板)
var 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='alert';
//为关闭按钮面板添加类
this.confirmBtn.className='a-close'
//为确定按钮面板添加类
this.closeBtn.className='a-confirm';
//为确定按钮添加文案
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(){
var me=this;
this.closeBtn.onclick=function(){
me.fail();
me.hide();
}
this.confirmBtn.onclick=function(){
me.success();
me.hide();
}
},
hide:function(){
this.panel.style.display='none';
},
show:function(){
this.panel.style.display='block';
}
}
观察者模式(发布-订阅模式)
- 如果遇到想在别人写的闭包功能模块的基础上加入自己的功能模块需求,又想和它们通信又不想让自己的模块和他人的模块严重耦合在一起的话可以考虑观察者模式
- 雏形:我们需要一个消息容器,我们需要确定注册的消息并可以注册消息的方法,我们需要取消注册消息的方法,我们需要发布消息的方法。
- 发布留言和与删除留言功能需求是用户主动触发,所以应该是观察者发布消息。而评论的追加和用户消息的增减是被动触发的,所以它们是订阅者去注册消息
- 基础雏形为:
var Observer=(function(){
var __message={};
return {
//注册消息接口
register:function(){},
//发布消息接口
fire:function(){},
//移除消息接口
remove:function(){}
}
})()
-
对于任意一个订阅者对象来说,其他订阅者对象的改变不会影响到自身。
-
对于每一个订阅者来说,其自身既可以是消息的发出者也可以是消息的执行者。
-
这只是依赖于调用观察者对象的三种方法(订阅消息、注销消息、发布消息)中的一种
-
观察者模式实现:
var Observer=(function(){
var __message={};
return {
//注册消息接口
register:function(type,fn){
//如果此消息类型不存在则创建一个给消息容器(该消息容器是数组格式,里面可能会有多条消息)
if(typeof __message[type]=="undefined"){
__message[type]=[fn]
}else{
//如果此消息类型存在,则将该消息推入消息容器中(此时的消息容器中肯定是多条消息)
__message[type].push(fn)
}
},
//发布消息接口
//args作为events.args里面的数据---events={type:type,args:参数数据||{}}
fire:function(type,args){
//消息类型没有注册无法发布
if(!__message[type]){
return;
}
//将来要接收的消息
var events={
type:type,
args:args || {}
};
var i=0;
var len=__message[type].length;
for(var i=0;i<len;i++){
//依次将执行注册的消息对应的动作序列
//这里的e.args:{msg:'发布消息'}
__message[type][i].call(this,events)
}
},
//移除消息接口
remove:function(type,fn){
if(__message[type] instanceof Array){
var i=__message[type].length-1;
for(;i>=0;i--){
__message[type][i]===fn && __message[type].splice(i,1);
console.log('已经删除')
}
}
}
}
})()
// Observer.register('test',function(e){
// console.log(e.type,e.args.msg)
// })
// Observer.fire('test',{msg:'发布消息'}) //test 发布消息
// Observer.remove('test',function(e){
// console.log(e.type,e.args.msg)
// }) //test 发布消息
// Observer.fire('test',{msg:'发布消息'})
- 案例:课堂上老师提问问题,积极的学生回答问题,打瞌睡的学生回答不了问题。老师可以当作是是发布者,学生可以是订阅者。
//学生回答老师提出的问题因此学生是订阅者
var Student = function(result){
var that=this;
//学生回答问题
that.result=result;
//学生回答问题动作
that.say=function(){
console.log(that.result)
}
}
//学生回答问题
Student.prototype.answer=function(question){
//注册参数问题
Observer.register(question,this.say)
}
//睡觉的学生无法回答问题,因此直接注销该方法
Student.prototype.sleep=function(question){
console.log(this.result + '' + question + '已被注销')
//取消对老师问题的监听
Observer.remove(question,this.say)
}
//教师类
var Teacher=function(){};
//教师提问问题的方法
Teacher.prototype.ask=function(question){
console.log('问题是 '+ question);
Observer.fire(question);
}
var student1=new Student('学生一回答问题,设计模式是xx');
student1.answer('什么是设计模式?');
var teacher=new Teacher();
teacher.ask('什么是设计模式?')
//问题是 什么是设计模式?
//学生一回答问题,设计模式是xx
状态模式
-
当一个对象的内部状态发生改变时,会导致其行为的改变。这看起来像是改变了对象
-
分支条件内部独立结果的管理,每一种条件作为对象内部的一种状态,面对不同判断结果。它起始就是选择对象内的一种状态。
-
对于状态模式,主要目的就是将条件判断的不同结果转化为状态对象的内部状态。一般作为状态对象内部的私有变量,然后提供一个能够调用状态对象内部状态的接口方法对象
-
案例一:
var Result=function(){
var _State={
state0:function(){console.log('第一种状态')},
state1:function(){console.log('第二种状态')},
state2:function(){console.log('第三种状态')}
}
function show(result){
_State['state'+result] && _State['state'+result]();
}
return {
show:show
}
}
// Result().show(2) //第三种状态
- 案例二:
//创建超级赛亚人状态类
var Human=function(){
//内部状态私有变量
var _currentState={};
//赛亚人动作与状态方法映射
var states={
jump:function(){console.log('跳跃')},
attack:function(){console.log('攻击')},
forward:function(){console.log('前进')}
}
//动作控制类
var Action={
//改变状态方法
changeState:function(){
//组合动作通过传递多个参数实现
var arg=arguments;
//重置内部状态
_currentState={};
//如果有动作则添加动作
if(arg.length){
//遍历动作
for(var i=0,len=arg.length;i<len;i++){
//向内部状态中添加动作---arg[i]作为属性名
_currentState[arg[i]]=true
}
}
//返回动作控制类
return this;
},
//执行动作
goes:function(){
console.log('触发了一次动作');
//遍历内部状态保存的动作
for(var i in _currentState){
//如果动作存在(动作保存在state状态中)则执行
states[i] && states[i]()
}
return this;
}
}
//返回接口方法 change\goes
return {
change:Action.changeState,
goes:Action.goes
}
}
//利用实例化状态类使用对状态类的复制品
var saiyan=new Human();
saiyan.change('jump','attack').goes().goes().change('forward').goes().change('xx').goes()
策略模式
- 将一组算法封装,使其相互之间可以替换。封装的算法具有一定独立性,不会随客户端变化而变化。
- 内部封装一个对象,然后通过返回的接口对象实现对内部对象的调用。不需要管理状态,状态间没有依赖关系。每次通过接口选择一种计谋来达到不同的效果
- Jquery的缓冲函数便是利用策略模式来实现的
$('div').animate({width:'20px',100,'linear'}) - 封装了代码簇并且封装的代码相互独立便于算法的重复引用,优化分支判断语句
- 案例一:
var PriceStates=(function(){
//内部算法对象
var internal={
//满200返50
return50:function(price){
return +price+parseInt(price/200)*50
},
//满300返80
return80:function(price){
return +price+parseInt(price/300)*80
},
//商品打五折
percent5:function(price){
return price * 100 * 80/10000
}
}
//向外暴露算法对象接口
return {
check:function(algorithm,price){
return internal[algorithm] && internal[algorithm](price)
},//添加策略
addStrategy:function(type,fn){
internal[type]=fn;
}
}
})()
var endP=PriceStates.check('return50',200)
console.log(endP) //250
PriceStates.addStrategy('isNumber',function(value){
return /^[0-9]+(\.[0-9]+)?$/.test(value) ? '是数字' : '请输入数字'
})
var endIsNum=PriceStates.check('isNumber',200)
console.log('isNum',endIsNum) //isNum 是数字
- 案例二:
//设置策略对象 awardA awardB awardC
var awardA=function(){}
awardA.prototype.calculate=function(award){
return award *5
}
var awardB=function(){}
awardB.prototype.calculate=function(award){
return award *4
}
var awardC=function(){}
awardC.prototype.calculate=function(award){
return award *3
}
// 定义奖金类
var Trophy=function(){
this.strategy=null //奖品等级对应的策略对象
}
Trophy.prototype.setSalary=function(salary){
this.salary=salary;
}
Trophy.prototype.setStrategy=function(strategy){
this.strategy=strategy; //设置奖品等级对应的策略对象
}
Trophy.prototype.get=function(){
if(!this.strategy){
throw new Error('未设置 strategy 属性')
}
return this.strategy.calculate(this.salary)
}
var bonus=new Trophy;
bonus.setSalary(1000);
bonus.setStrategy(new awardA());
console.log(bonus.get())
职责链模式
- 解决请求的发送者与请求的接收者之间的耦合。通过职责链上的多个对象对分解请求流程,实现请求在多个对象之间的传递,直到最后一个对象完成请求的处理
- 将需求颗粒化,把完整的需求分解为一部分一部分相互独立的模块需求,对象和对象之间不会相互影响
- 案例:
var take6=function(takeType,pay,stock){
if(takeType==6 && pay===true){
console.log('takeType is 6')
}else{
return 'next' //传递给职责链的下一个
}
}
var take7=function(takeType,pay,stock){
if(takeType==7 && pay===true){
console.log('takeType is 7')
}else{
return 'next' //传递给职责链的下一个,请求向后面传递
}
}
var lastTake=function(takeType,pay,stock){
if(stock>0){
console.log('stoke > 0')
}else{
console.log('stoke < 0')
}
}
var ChainOfRes=function(fn){
this.fn=fn;
//表示链中的下一个节点
this.nextSuccessor=null;
}
//指定在链中的下一个节点
//传入的是一个实例化对象
// this.nextSuccessor=实例化对象
ChainOfRes.prototype.setNextSuccessor=function(successor){
return this.nextSuccessor=successor;
}
//传递请求给某个节点
ChainOfRes.prototype.passRequest=function(){
//先执行了自己
var ReturnResult;
var ReturnResult=this.fn.apply(this,arguments);
//如果有下一个节点在去执行下一节点,直到没有下一节点
if(ReturnResult==='next'){
console.log('111',this.nextSuccessor)
return this.nextSuccessor && this.nextSuccessor.passRequest.apply(this.nextSuccessor,arguments)
};
return ReturnResult;
}
var chainTake6=new ChainOfRes(take6);
var chainTake7=new ChainOfRes(take7);
var chainLastTake=new ChainOfRes(lastTake);
console.log(chainTake6)
/**
* takeType is 6
takeType is 7
stoke > 0
stoke < 0
*/
命令模式
- 将请求与实现解耦并封装成独立对象,从而使不同的请求对客户端的实现参数化
- 命令模式是将创建模块的逻辑封装在一个对象里,这个对象提供一个参数化的请求接口,通过调用这个接口并传递一些参数实现调用命令对象内部的一些方法
- 要实现哪些需求,哪些需求是可以命令化的。 命令模式是将执行的命令封装,解决命令的发起者与命令的执行者之间的耦合
- 每一条命令实质上是一个操作,命令的使用者不必了解命令的执行者(命令对象)的命令接口时如何实现的、接受的、执行的。所有的命令都存储在命令对象中
- 案例:
var shoot={
execute:function(){
console.log('射击')
}
}
var move={
execute:function(){
console.log('移动')
}
}
var attack={
execute:function(){
console.log('打人')
}
}
var MacroCommand=function(){
return {
commandList:[],
//添加一个命令
add:function(command){
this.commandList.push(command);
},
//执行添加上的命令操作
execute:function(){
for(var i=0;i<this.commandList.length;i++){
this.commandList[i].execute();
}
},
//取消上一步添加的操作
revocation:function(){
this.commandList.splice(this.commandList.length-1,1)
}
}
}
var macro=MacroCommand();
macro.add(attack)
macro.add(move)
macro.add(shoot)
macro.revocation()
macro.execute()
/**
* 打人
移动
// 射击
*/
访问者模式
- 针对于对象结构的元素,定义在不改变该对象的前提下访问结构中元素的新方法
function bindIEEvent(dom,type,fn,data){
var data=data || {};
dom.attachEvent('on'+type,function(e){
fn.call(dom,e,data)
})
}
中介者模式
- 通过中介者对象封装一系列对象之间的交互。使得对象之间不在相互引用。中介者模式消息的发送方只有一个,就是中介者对象。
- 而且中介者不能订阅消息,只有那些活跃对象(订阅者)才可订阅中介者的消息
- 案例:
var Mediator = (function(){
//消息对象
var _msg={};
return {
register:function(type,action){
//如果消息存在
if(_msg[type]){
_msg[type].push(action)
}else{
//不存在则创建消息容器
_msg[type]=[];
//存入新消息回调函数
_msg[type].push(action)
}
},
send(type){
//如果消息已经被订阅
if(_msg[type]){
//遍历已经存储的消息回调函数
for(var i=0,len=_msg[type].length;i<len;i++){
//执行回调函数
_msg[type][i] && _msg[type][i]()
}
}
}
}
})()
Mediator.register('test',function(){
console.log('test1')
})
Mediator.register('test',function(){
console.log('test2')
})
Mediator.send('test') //test1 test2
备忘录模式
- 在不破坏对象的封装性的前提下,在对象之外捕获并保存该对象内部的状态以便以后对象使用或者对象恢复到以前的某个状态。
- 可以缓存请求过的数据,每次在遇到发送请求的时候对当前状态做一次记录,比如可以将已经请求下来的数据以及对应的页面的标记点等有效信息缓存下来,将来如果需要直接在缓存中查询即可。
- 案例:
//Page备忘录类
var Page=function(){
//信息缓存对象
var cache={};
return function(page,fn){
//判断该page是否在缓存类中
if(cache[page]){
//利用该缓存做事……;
//执行回调函数
fn && fn();
}else{
//如果没有缓存则重新拉取数据
//拉取完数据后将cache[page]=拉取后的数据
//再执行回调函数
fn && fn()
}
}
}
迭代器模式
- 再不暴露对象内部结构的同时,可以顺序地访问聚合对象内部的元素.无论是内部迭代器还是外部迭代器,
- 只要被迭代的聚合的对象拥有length属性而且可以用下标访问那么它就可以被迭代
- 案例:
var A={
client:{
user:{
username:'lth',
uid:'123'
}
}
}
console.log(A['client']['user']['username'])
//console.log(A.client.user?.usernamesss) //undefined
//同步变量迭代器取值器
AGetter=function(key){
//如果不存在A则返回未定义
if(!A){
return undefined;
}
var result=A; //获取变量列表
key=key.split('.');
//迭代同步变量A对象属性
//console.log(A['client']['user']['username'])
for(var i=0,len=key.length;i<len;i++){
if(result[key[i]]!==undefined){
result=result[key[i]]
}else{
return undefined
}
}
//返回获取的结果
return result;
}
console.log(AGetter('client.user.uid')) //123
- 数组迭代器
//数组迭代器
var eachArray=function(arr,fn){
var len=arr.length;
//遍历数组
for(var i=0;i<len;i++){
//可以通过让回调函数返回false手动控制迭代器何时结束
if(fn.call(arr[i],i,arr[i])===false){
break;
}
}
}
var arr=[1, 2, 3, 4, 5];
eachArray(arr,function(i,data){
console.log(i,data);
return false;
})
- 对象迭代器
var eachObj=function(arr,fn){
var len=arr.length;
//遍历对象的每一个属性
for(var i in obj){
//可以通过让回调函数返回false手动控制迭代器何时结束
if(fn.call(obj[i],i,obj[i])==false){
break;
}
}
}
var obj={
a:23,
b:24
}
eachObj(obj,function(i,data){
console.log(i,data)
})
- 数组迭代器另一种写法(不能控制迭代器何时结束)
var each=function(arr,fn){
for(var i=0,l=arr.length;i<l;i++){
fn.call(arr[i],i,arr[i])
}
}
each([1,2,3],function(i,n){
console.log(i,n)
})
- 对象迭代器另一种写法(不能控制迭代器何时结束)
var eachObject=function(obj,fn){
for(var i in obj){
fn.call(obj[i],i,obj[i])
}
}
eachObject({a:1,b:{c:1}},function(i,v){
console.log(i,v)
})
- 利用迭代器判断两个数组是否相等
var Iterator=function(obj){
var current=0;
var next=function(){
current+=1
}
var isDone=function(){
return current>=obj.length
}
var getCurrItem=function(){
return obj[current]
}
return {
next:next,
isDone:isDone,
getCurrItem:getCurrItem,
length:obj.length
}
}
//比较两个obj(数组)是否相等
var compare=function(i1,i2){
if(i1.length!=i2.length){
console.log('不相等')
}
//当两者为false说明还未比较完应继续调用next()方法
while(!i1.isDone() && ! i2.isDone()){
if(i1.getCurrItem() !== i2.getCurrItem()){
console.log('不相等')
}
i1.next()
i2.next()
}
console.log('相等')
}
- 倒序访问的迭代器
var reverseEach=function(arr,fn){
for(var l=arr.length-1;l>=0;l--){
fn(l,arr[l])
}
}
reverseEach([1,2,3],function(i,n){
console.log(n) //3 2 1
})