行为型设计模式
不同对象之间职责划分或算法抽象,类或对象之间的交流模式并加以实现
1.模板方法模式
父类定义一组操作算法骨架,将实现步骤延迟到子类中
子类可以不改变父类算法结构的同时重新定义算法中某些实现步骤
1.1提示框归一化
将多个模型抽象化归一,从中提取出来一个最基本的模板
可以作为实体对象也可以作为抽象对象
其他模板只需要继承这个模板方法,也可以拓展方法
1.2创建基本提示框
//模板类 基础提示框 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.closeBtn.className = 'a-close';
//为确定按钮添加类
this.confirmBtn.className = 'a-confirm';
//为确定按钮添加文案
this.confirmBtn.innerHTML = data.confirm || '确认';
//为提示内容添加文本
this.contentNode.innerHTML = this.content;
//点击确定按钮执行方法 如果data中有success方法则为success方法,否则为空函数
this.success = data.success || function(){};
//点击关闭按钮执行方法
this.fail = data.fail || function(){};
}
1.3模板的原型方法
基本提示框可创建,应该具有一些基本方法
init方法来组装提示框,bindEvent方法来绑定点击确定或关闭按钮事件
//提示框原型方法
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.display = 'block';
}
}
1.4根据模板创建类
有了这个提示框基类,拓展其他类型则容易很多
//标题提示框
var TitleAlert = function(data){
//继承基本提示框构造函数
Alert.call(this, data);
//设置标题内容
this.title = data.title;
//创建标题组件
this.titleNode = document.createElement('h3');
//标题组件中写入标题内容
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);
}
1.5继承类也可以作为模板类
//带有取消按钮的弹出框
var CancelAlert = function(data){
//继承标题提示框构造函数
TitleAlert.call(this, data);
//取消按钮文案
this.cancel = data.cancel;
//创建取消按钮
this.cancelBtn = document.createElement('span');
//为取消按钮添加类
this.cancelBtn.className = 'cancel';
//设置取消按钮内容
this.cancelBtn.innerHTML = this.cancel || '取消';
}
//继承标题提示框原型方法
CancelAlert.prototype = new Alert();
CancelAlert.prototype.init = function(){
//继承标题提示框创建方法
TitleAlert.prototype.init.call(this);
//由于取消按钮要添加在末尾,创建完其他组件后添加
this.panel.appendChild(this.cancelBtn);
}
CancelAlert.prototype.bindEvent = function(){
var me = this;
//标题提示框绑定事件方法继承
TitleAlert.prototype.bindEvent.call(me);
//取消按钮绑定事件
this.cancelBtn.onclick = function(){
//执行取消回调函数
me.fail();
//隐藏弹层
me.hide();
}
}
1.6创建一个提示框
new CancelAlert({
title : 'xx',
content : 'xx',
success : function(){
console.log('ok');
},
fail : function(){
console.log('cancel')
}
}).init();
1.7创建多类导航
2.观察者模式
发布-订阅者模式或消息机制
定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合
2.1实现自己的需求,又不想新添加的代码影响其他人实现的功能
2.2创建一个观察者
有一个消息容器,和三个方法:
订阅消息方法、取消订阅消息方法、发送订阅的消息方法
//将观察者放在闭包中,页面加载就立即执行
var Observer = (function(){
var _message = {};
return{
//注册信息接口
regist : function(){},
//发布信息接口
fire : function(){},
//移除信息接口
remove : function(){}
}
})();
1.注册信息接口
将订阅者注册的消息推入到消息队列中
接受两个参数:消息类型以及相应的处理动作
regist : function(type, fn){
//如果此消息不存在则创建一个消息类型
if(typeof __message[type] === 'undefined'){
//将动作推入到该消息对应的动作执行队列
__message[type] [fn];
}else{
//如果消息存在,将动作推入到该消息对应的动作执行队列
__message[type].push(fn);
}
}
2.发布信息接口
fire : function(type, args){
//如果消息没有被注册则返回
if(!__message[type])
return;
//定义消息内容
var events = {
type : type,
args : args || {}
},
i = 0,
len = __message[type].length;
//遍历消息动作
for(; i < len; i++){
依次执行注册的消息对应的动作序列
__message[type][i].call(this, events);
}
}
3.移除信息接口
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);
}
}
}
2.3思考
发布留言与删除留言是用户主动触发,这是观察者发布消息
评论的追加和用户信息的增减都是被动触发的,是订阅者去注册消息
2.4完成
//外观模式 简化获取元素
function $(id){
return document.getElementById(id);
}
//工程师A
(function(){
追加一条消息
function addMsgItem(e){
var text = e.args.text, //获取消息中用户添加的文本内容
ul = $('msg'), //留言容器元素
li = document.createElement('li'), //创建内容容器元素
span = document.createElement('span'); //删除按钮
li.innerHTML = text; //写入评论
//关闭按钮
span.onclick = function(){
ul.removeChild(li); //移除留言
//发布删除留言消息
Observer.fire('removeCommentMessage', {
num : -1
});
}
//添加删除按钮
li.appendChild(span);
//添加留言节点
ul.appendChild(li);
}
//注册添加评论信息
Observer.regist('addCommentMessage', addMsgItem);
})();
//工程师B
(function(){
//更改用户消息数目
function changeMsgNum(e){
//获取需要增加的用户消息数目
var num = e.args.num;
//增加用户消息数目并写入页面
$('msg_num').innerHTML = parseInt($('msg_num').innerHTML) + num;
}
//注册添加评论信息
Observer
.regist('addCommentMessage', changeMsgNum)
.regist('removeCommentMessage', changeMsgNum);
})();
//工程师C
(function(){
//用户点击提交按钮
$('user_submit').onclick = function(){
//获取用户输入框中输入的信息
var text = $('user_input');
//消息为空则提交失败
if(text.value === ''){
return;
}
//发布一则评论消息
Observer.fire('addCommentMessage', {
text : text.value,
num : 1
});
text.value = '';
}
})();
解决了各个模块的耦合问题,属于哪个模块就写在原模块,不需要担心其他模块怎么实现
只需要收发消息即可
2.5对象间解耦
3.状态模式
当一个对象内部的状态发生了改变,导致其行为发生了改变,看起来像改变了对象
3.1将条件判断的结果转化为状态对象的内部状态,提供一个可以调用状态对象内部状态的接口方法
3.2
//创建超级玛丽状态类
var MarryState = function(){
//内部状态私有变量
var _currentState = {},
states = {
//动作与状态方法映射
jump : function(){
//跳跃
console.log('jump');
},
move : function(){
//移动
console.log('move');
},
shoot : function(){
//射击
console.log('shoot');
},
squat : function(){
//蹲下
console.log('squat');
}
};
//动作控制类
var Action = {
//改变状态方法
changeState : function(){
//组合动作通过传递多个参数实现
var arg = arguments;
//重置内部状态
_currentState = {};
//如果有动作则添加动作
if(arg.length){
//遍历动作
for(var i = 0, len = arg.length; i < len; i++){
//向内部状态中添加动作
_currentState[arg[i]] = true;
}
}
//返回动作控制类
return this;
},
//执行动作
goes : function(){
console.log('触发一次动作');
//遍历内部状态保存的动作
for(var i in _currentState){
//如果动作存在则执行
states[i] && states[i]();
}
return this;
}
}
//返回接口方法
return{
change : Action.changeState,
change : Action.goes
}
}
3.3使用
var marry = new MarryState();
marry
.change('jump', 'shoot')
.goes();
//改变一个状态,就改变了执行结果,好像改变了一个对象
4.策略模式
将定义的一组算法封装起来,相互之间可以替换
封装的算法有一定的独立性,不会随客户端变化而变化
4.1结构上与状态模式很类似
不同在于不需要管理状态,状态之间没有依赖关系,策略之间可以相互替换,保存着相互独立的算法
4.2策略对象
var PriceStrategy = function(){
//内部算法对象
var stragtegy = {
//满100返30
return30 : function(price){
return +price + parseInt(price / 100) * 30;
},
//满100返50
return50 : function(price){
return +price + parseInt(price / 100) * 50;
},
//9折
percent90 : function(price){
return price * 100 *90 / 10000;
}
}
//策略算法调用接口
return function(algorithm, price){
//如果算法存在,则调用算法,否则返回false
return stragtegy[algorithm] && stragtegy[algorithm](price)
}
}();
5.职责链模式
解决请求的发送者与请求的接收者之间的耦合
通过职责链上的多个对象对分解请求流程,实现请求在多个对象之间的传递
直到最后一个对象完成请求的处理
5.1"半成品"需求
需求不确定,以后还会修改
5.2分解需求
将每件事情独立出一个模块对象去处理
分解成一部分相互独立的模块需求,通过这些对象的分工协作
每个对象只是做自己分内的事情,无关就传到下一个对象中去做,直到需求完成
5.3.第一站-请求模块
5.4下一站-响应数据适配模块
5.5终点站-创建组件模块
5.6单元测试
对程序的每个独立单元在不同种运行环境下进行逻辑测试
比如可能接受哪些数据,并对结果进行预测
5.7定义了请求的传递方向,通过多个对象对请求的传递,实现复杂的逻辑操作
对于每一个对象来说都可能是请求的发起者也可能是请求的接收者
6.命令模式
将请求与实现解耦并封装成独立对象,从而使不同的请求对客户端的实现参数化
6.1将创建模块的逻辑封装在一个对象里,这个对象提供一个参数化数据接口
6.2命令对象
//模块实现模块
var viewCommand = (function(){
//方法集合
var Action = {
//创建方法
create : function(){},
//展示方法
display : function(){}
}
//命令接口
return function excute(){}
})();
6.3视图创建
//模块实现模块
var viewCommand = (function(){
var tpl = {
//展示图片结构模板
product : [
'<div>',
'<img src="{#src#}"/>',
'<p>{#text#}</p>',
'</div>'
].join(''),
//展示标题结构模块
title : [
'<div class="title">',
'<div class="main">',
'<h2>{#title#}</h2>',
'<p>{#tips#}</p>',
'</div>',
'</div>'
].join('')
},
//格式化字符串缓存字符串
html = '';
//格式化字符串
//'<div>{#content#}</div>'用{content: 'demo'}代替后可得到
//'<div>demo</div>'
function formateString(str, obj){
//替换'{#'与'#}'之间的字符串
return str.replace(/\{#(\w+)#\}/g, function(match, key){
return obj[key];
})
}
//方法集合
var Action = {}
//命令接口
return function excute(){}
})();
create : function(data, view){
//解析数据 如果数据是一个数组
if(data.length){
//遍历数组
for(var i = 0, len = data.length; i < len; i++){
//将格式化之后的字符串缓存到html中
html += formateString(tpl[view], data[i]);
}
}else{
//直接格式化字符串缓存到html中
html += formateString(tpl[view], data);
}
}
//展示方法
display : function(container, data, view){
//如果传入数据
if(data){
//根据给定数据创建视图
this.create(data, view);
}
//展示模块
document.getElementById(container).innerHTML = html;
//展示后清空缓存的字符串
html = '';
}
//命令接口
return function excute(msg){
msg.param = Object.prototype.toString.call(msg.param) === "[object Array]" ?
msg.param : [msg.param];
Action[msg.command].apply(Action, msg.param)
}
7.访问者模式
针对于对象结构中的元素,定义在不改变该对象的前提下访问结构中元素的新方法
7.1自娱自乐的IE
this指向的不是这个元素而是window
7.2访问操作元素
不改变操作对象的同时,为它添加新的操作方法-事件
function bindIEEvent(dom, type, fn, data){
var data = data || {};
dom.attachEvent('on' + type, function(e){
fn.call(dom, e, data);
});
};
call/apply可以更改函数执行时的作用域
让某个对象在其他作用域中运行
比如事件源对象在回调函数中的作用域运行,那么回调函数访问的this就是事件源对象
7.3为对象添加属性数据没有次序,如果可以像处理数组一样来处理一个对象
7.4对象访问器
//访问器
var Visitor = (function(){
return {
//截取方法
splice : function(){
//splice方法参数,从原参数的第二个参数开始算起
var args = Array.prototype.splice.call(arguments, 1);
//对第一个参数对象执行splice方法
return Array.prototype.splice.apply(arguments[0], args);
},
//追加数据方法
push : function(){
//强化类数组对象,使他拥有length属性
var len = arguments[0].length || 0;
//添加的数据从原参数的第二个参数算起
var args = this.splice(arguments, 1);
//校正length属性
arguments[0].length = len + arguments.length - 1;
//对第一个参数对象执行push方法
return Array.prototype.push.apply(arguments[0], args);
},
//弹出最后一次添加的元素
pop : function(){
//对第一个参数对象执行pop方法
return Array.prototype.pop.apply(arguments[0]);
}
}
})();
8.中介者模式
中介者对象封装一系列对象之间的交互,使对象之间不再相互引用,降低耦合
8.1消息收发机制
将消息系统封装在中介者对象内部,中介者对象只能是消息的发送者
8.2中介者对象
//中介者对象
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 : function(type){
//如果该消息已经被订阅
if(_msg[type]){
//遍历已存储的消息回调函数
for(var i = 0, len = _msg[type].length; i < len; i++){
//执行该回调函数
_msg[type][i] && _msg[type][i]();
}
}
}
}
}();
8.3订阅消息
8.4发布消息
9.备忘录模式
在不破坏对象的封装性的前提下,在对象之外捕获并保存该对象内部状态
以便日后对象使用或者恢复到以前的某个状态
9.1点击下一页后又点击上一页,请求多余,第一次已经获取了数据,不需要再发送多余请求
9.2缓存数据
将请求过的数据做一次缓存,再次访问直接在缓存中查询
9.3新闻缓存器
//Page备忘录类
var Page = function(){
//信息缓存对象
var cache = {};
return function(page, fn){
//判断该叶数据是否在缓存中
if(cache[page]){
//恢复到该页状态 显示该页内容
showPage(page, cache[page]);
//执行成功回调函数
fn && fn();
}else{
//若缓存Cache中无该页数据
$.post('./data/getNewsData.php',{
//请求携带数据page页码
page : page
}, function(res){
//成功返回
if(res.errNo == 0){
//显示该页数据
showPage(page, res.data);
//将该页数据种入缓存中
cache[page] = res.data;
//执行成功回调函数
fn && fn();
}else{
//处理异常
}
})
}
}
}()
10.迭代器模式
在不暴露对象内部结构的同时,可以顺序访问聚合对象内部的元素
优化循环语句,使程序清晰可读
10.1迭代器
顺序访问一个聚合对象内部的元素
比如焦点图其实就是一组聚合对象,创建一个迭代器,专门访问图片数据
比如前一张、后一张、第一张、最后一张,对每一张图片处理,对当前图片处理
//迭代器
var Iterator = function(items, container){
//获取父容器,若container参数存在,并且可以获取,否则获取document
var container = container && document.getElementById(container) || document,
//获取元素
items = container.getElementsByTagName(items),
//获取元素长度
length = items.length,
//当前索引值,默认0
index = 0;
//缓存源生数组splice方法
var slice = [].splice;
return {
//获取第一个元素
first : function(){},
//获取最后一个元素
second : function(){},
//获取前一个元素
pre : function(){},
//获取后一个元素
next : function(){},
//获取某一个元素
get : function(){},
//对每一个元素执行某一个方法
dealEach : function(){},
//对某一个元素执行某一个方法
dealItem : function(){},
//排他方式处理某一个元素
exclusive : function()
}
}
10.2方法的实现
first : function(){
index = 0;
return items[index];
},
second : function(){
index = length - 1;
return items[index];
},
pre : function(){
if(--index > 0){
return items[index];
}else{
index = 0;
return null;
}
},
next : function(){
if(++index < length){
return items[index];
}else{
index = length - 1;
return null;
}
},
get : function(num){
index = num >= 0 ? num % length : num % length + length;
return items[index];
},
dealEach : function(fn){
var args = splice.call(arguments, 1);
for(var i = 0; i < length; i++){
fn.apply(items[i], args);
}
},
dealItem : function(num, fn){
fn.apply(this.get(num), splice.call(arguments, 2))
},
exclusive : function(num, allFn, numFn){
this.dealEach(allFn);
if(Object.prototype.toString.call(num) === "object Array"){
for(var i = 0, len = num.length; i < len; i++){
this.dealItem(num[i], numFn);
}
}else{
this.dealItem(num, numFn);
}
}
10.3使用
var demo = new Iterator('li', 'container');
console.log(demo.first());
10.4数组迭代器
var eachArray = function(arr, fn){
var i = 0,
len = arr.length;
//遍历数组
for(; i < len; i++){
//一次执行回调函数
if(fn.call(arr[i], i, arr[i]) === false){
break;
}
}
}
//试用
eachArray(arr, function(i, data){
console.log(i, data);
});
10.5.对象迭代器
var eachObject = function(obj, fn){
//遍历对象中的每一个属性
for(var i in obj){
//一次执行回调函数
if(fn.call(obj[i], i, obj[i]) === false){
break;
}
}
}
10.6同步变量迭代器
操作页面中的同步变量(页面加载时打印到页面中的变量)内的某些属性值
不知道服务器是否将该属性值或者该属性的上级属性正确的打印到页面中
校验需要一层一层的安全校验,迭代器可以减少编写校验代码
//同步变量
var A = {
//所有用户共用
common : {},
client : {
user : {
username : '雨夜清荷',
uid : '123'
}
},
server : {}
};
//同步变量取值器
AGetter = function(key){
if(!A)
return undefined;
var result = A;
key = key.split('.');
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.username'));
//同步变量赋值器
ASetter = function(key, val){
if(!A)
return false;
var result = A;
key = key.split('.');
for(var i = 0, len = key.length; i < len - 1; i++){
if(result[key[i]] === undefined){
result[key[i]] = {};
}
if(!(result[key[i]] instanceof Object)){
throw new Error('A.' + key.splice(0,i+1).join('.') + 'is not Object');
return false;
}
result = result[key[i]];
}
return result[key[i]] = val;
}
10.7.分支循环嵌套问题
11.解释器模式
对于一种语言,给出其文法表示形式,并定义一种解释器
通过使用这种解释器来解释语言中定义的句子
11.1获取button相当于整个页面文档的XPath路径为HTML>BODY|HEAD>BUTTON
11.2冒泡遍历整个文档树
//XPath解释器
var Interpreter = (function(){
//获取兄弟元素名称
function getSublingName(node){
//....
}
return function(node, wrap){
//路径数组
var path = [],
//如果不存在容器节点,默认为document
wrap = wrap || document;
//如果当前目标节点等于容器节点
if(node === wrap){
//容器节点为元素
if(wrap.nodeType == 1){
//路径数组中输入容器节点名称
path.push(wrap.nodeName.toUpperCase());
}
//返回最终路径数组结果
return path;
}
//如果当前节点的父节点不等于容器节点
if(node.parentNode !== wrap){
//对当前节点的父节点执行遍历操作
path = arguments.callee(node.parentNode, wrap);
}
//如果当前节点的父元素节点与容器节点相等
else{
//容器节点为元素
if(wrap.nodeType == 1){
//路径数组中输入容器节点名称
path.push(wrap.nodeName.toUpperCase());
}
}
//获取元素的兄弟元素名称统计
var sublingNames = getSublingName(node);
//如果节点为元素
if(node.nodeType == 1){
//输入当前节点元素名称及其前面兄弟元素名称统计
path.push(node.nodeName.toUpperCase() + sublingNames);
}
//返回最终路径数组结果
return path;
}
//立即执行方法
})();
//获取兄弟元素名称
function getSublingName(node){
//如果存在兄弟元素
if(node.previousSibling){
var name = '', //返回的兄弟元素名称字符串
count = 1, //紧邻兄弟元素中相同名称元素个数
nodeName = node.nodeName, //原始节点名称
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 '';
}
}
11.3使用
var path = new Interpreter(document.getElementById('span7'));
console.log(path.join('>'));