行为设计模式用于不同对象之间职责划分、算法抽象,包含:模板方法模式、观察者模式、状态模式、策略模式、职责链模式、命令模式、访问者模式、中介模式、备忘录模式、迭代器模式、解释器模式。
模板方法模式(Template Method)
在父类中定义操作算法骨架,把一些实现步骤延迟到子类中,让子类可以不修改父类的算法结构的同时能重新定义算法中某个实现步骤。
模板方法模式就是把很多个模型抽象化归一,从中抽象提取一个最基本的模板(该模板可以作为实体对象也可以作为抽象对象),其他模块只需继承该模板,拓展一些方法即可。
比如一个网站中所有页面的弹窗要统一样式,则可以把页面中弹窗的基本公有的样式统一。先创建一个基本的提示框基类,其他的提示框只需要在继承的基础上进行拓展。
/**
* 模板方法模式
*/
//模板类 提示框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;
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 that = this;
this.closeBtn.onclick = function(){
that.success();
that.hide();
}
this.confirmBtn.onclick = function(){
that.success();
that.hide();
}
},
hide:function(){
this.panel.style.dispaly = "none";
},
show:function(){
this.panel.style.dispaly = "block";
}
}
<body>
<div id="content"></div>
<script src="js/utils.js"></script>
<script>
//右侧按钮提示框
var RightAlert = function(data){
Alert.call(this,data);
this.confirmBtn.className = this.confirmBtn.className+' right';
}
RightAlert.prototype = new Alert();
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);
Alert.prototype.init.call(this);
}
//取消按钮
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 that = this;
TitleAlert.prototype.bindEvent.call(that);
this.cancelBtn.onclick = function(){
that.fail();
that.hide();
}
}
new CancelAlert({
title:"提示标题",
content:"提示内容",
success:function(){
console.log("ok");
},
fail:function(){
console.log("canpel");
}
}).init();
function formateString(str,data){
return str.replace(/\{#(\w+)#\}/g,function(match,key){
return typeof data[key]=== undefined ? "" : data[key];
})
}
var Nav = function(data){
this.item = '<a href="{#href#}" title="{#title#}">{#name#}</a>';
this.html = "";
for(var i=0,len= data.length;i<len;i++){
this.html += formateString(this.item,data[i]);
}
return this.html;
}
var NumNav = function(data){
var tpl = '<b>{#num#}</b>';
for(var i=data.length-1;i>= 0;i--){
data[i].name += data[i].name + formateString(tpl,data[i]);
}
return Nav.call(this,data);
}
var LinkNav = function(data){
var tpl = '<span>{#link#}</span>';
for(var i =data.length-1;i>=0;i--){
data[i].name +=data[i].name + formateString(tpl,data[i]);
}
return Nav.call(this,data);
}
var nav = document.getElementById("content");
nav.innerHTML = NumNav([
{
href:"http://wyysz.com",
title:"读心,读自己",
name:"读心",
num:'10'
}
])
</script>
</body>
观察者模式(Observer)
又称发布-订阅者模式或者是消息机制,制定一个依赖的关系,解决了主体对象和观察者的之间功能的耦合。
/**
* 观察者模式
*/
//把观察者放到闭包中,页面加载就立即执行
var Observer = (function(){
var __messages = {};
return {
/**
* 注册信息的接口,把订阅者注册的消息推入消息队列中
* @param {*} type 消息类型
* @param {*} fn 相应的处理动作
*/
regist:function(type,fn){
//如果消息不存在,则是创建一个该消息类型
if(typeof __messages[type]==='undefined'){
//把动作放入该消息对应的动作执行队列中
__messages[type] = [fn];
}else{
//如果该消息不存在,则把动作方法加入该消息对应的动作执行序列中
__messages[type].push(fn);
}
},
/**
* 发布信息接口,观察者发布消息时,订阅者订阅的消息一次执行,
* @param {*} type 消息类型
* @param {*} args 动作在执行时需要的参数
*/
fire:function(type,args){
//消息没有被注册,则返回
if (!__messages[type]) {
return ;
}
//定义消息信息
var events = {
type:type,
args:args || {} //消息中携带的数据
};
var i = 0;
var len = __messages[type].length;
for(;i<len;i++){
//依次执行注册的消息对应的动作序列
__messages[type][i].call(this,events);
}
},
/**
* 移除信息接口,订阅者注销的消息从信息队列中移除,需要两个参数。其中先检验该消息是否存在
* @param type 消息的类型
* @param fn 某一个动作的方法
* */
remove:function(type,fn){
//
if (__messages[type] instanceof Array) {
//从最后一个消息动作开始遍历
var i = __messages[type].length - 1;
for(;i>=0;i--){
//该动作存在,则在信息动作序列中移除
}
}
}
}
})();
添加消息的实例:
/**
* 追加消息的实例
*/
function $(id){
return document.getElementById(id);
}
//A
(function(){
//追加信息
function addMsgItem(e){
var text = e.args.text; //用户添加的文本内容
var ul = $("msg"); // 留言容器元素
var li = document.createElement("li"); //创建内容容器
var 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);
Observer.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 = ""
}
})();
状态模式(State)
一个对象内部状态发生改变时,会导致其行为发生改变。状态管理模式很适合用于分支条件内部独立结果的管理,每一种条件作为对象内部的一种状态,不同的结果就是选择了对象内的一种状态。
状态对象的内部状态一般作为对象内部的私有变量,再提供一个能够调用状态对象内部状态的接口方法。如:
/**
* 状态模式
* 一个投票结果状态的实例
*/
//投票结果状态对象
var ResultState = function(){
//把结果保存在内部状态中
var State = {
//每一一种状态作为一个独立的方法保存
state0:function(){
console.log("状态1");
},
state1:function(){
console.log("状态2");
}
}
function show(result){
State['state'+result] && State["state"+result]();
}
return {
show:show
}
}();
ResultState.show(0);//状态1
/**
*
*/
var MarryState = function(){
//内部私有变量
var _currentState = {},
states = {
jump:function(){
console.log("跳跃");
},
move:function(){
console.log("移动");
},
shoot:function(){
console.log("射击");
},
squat:function(){
console.log("蹲下");
}
};
//动作控制类
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;
}
}
//返回接口change,goes
return {
change:Action.changeState,
goes:Action.goes
}
}
var m = new MarryState();
m.change("jump","move")
.goes()
.goes()
.change("shoot")
.goes();
策略模式(Strategy)
把定义的一组算法封装起来,让它们之间可以相互替换。封装的算法具有一些独立性,不会随着客户端变化而变化。和状态模式很像。都是在内部封装一个对象,再通过返回的接口对象实现对内部对象的调用,但是策略模式不需要管理状态,,状态之间没有依赖,策略之间可以相互替换。
例如:在大促销互动中,分别有5折出售、8折出售和9折出售,普通用户满100返39,VIP用户满100返50。其中一种商品只有一种促销策略,不用考虑其他促销状态。
/**
* 商品促销活动中的策略模式
*/
var PriceStrategy = function(){
//内部算法
var stragtagy = {
//100返39
return39:function(price){
return +price + parseInt(price/100)*39;
},
//100返50
return50:function(price){
return +price + parseInt(price/100)*50;
},
//9折
percent90:function(price){
return price * 9/10
},
//8折
percent80:function(price){
return price*8/10
},
//5折
percent50:function(price){
return price*5/10;
}
}
//策略算法调用接口
return function(algorithm,price){
return stragtagy[algorithm] && stragtagy[algorithm](price)
}
}();
var p = PriceStrategy("return50","200");
console.log(p);
/**
* 表单验证
*/
var InputStrategy = function(){
var strategy = {
//判断是否为空
notNull:function(value){
return /\s+/.test(value) ? "请输入内容" : " "
},
//Number
number:function(value){
return /^[0-9]+(\.[0-9]+)?$/.test(value) ? '' : "请输入数字";
},
//phone
phone:function(value){
return /^\d{3}\-\d{8}$|^\d{4}\-\d{7}$/.test(value) ? "":"请正确输入电话号码格式,如001-12345678或者0418-1234567"
}
}
return {
//验证接口
check:function(type,value){
value = value.replace(/^\s+|\s+$/g,"");
return strategy[type] ? strategy[type](value) : "没有该类型的检测方法"
},
//添加策略
addSrategy:function(type,fn){
strategy[type] = fn;
}
}
}();
//拓展策略
InputStrategy.addSrategy("nickname",function(value){
return /^[a-zA-Z]\w{3,7}$/.test(value) ? "" : "请输入4-8位昵称!"
});
/**
* 外观模式简化元素的获取
*/
function $tag(tag,context){
context = context || document;
return context.getElementsByTagName(tag);
}
职责链模式
处理请求者和发送者之间的耦合,通过职责链上的多个对象分解请求流程,实现请求在多个对象之间的传递,直到最后一个对象完成请求。
常见的场景,如页面中的输入验证和输入提示交互验证,用户在输入框输入信息后,在输入框下边提示一些备选项,在用户输入结束时,就要对用户输入的信息进行验证,页面中有很多的模块需要用户提交信息的操作,大部分的输入框都需要输入验证和输入提示的功能。
/**
* 异步请求对象(简化版本)
* @param data 请求数据
* @param dealType 响应数据处理对象
* @param dom 事件源
*/
var sendData = function(data,dealType,dom){
var xhr = new XMLHttpRequest();
var url = "路径";
xhr.onload = function(event){
if ((xhr.status >=200 && xhr.status<300) || xhr.status == 304) {
dealData(xhr.responseText,dealType,dom);
}else{
console.log("请求失败");
}
};
for(var i in data){
url += '&'+i+"="+data[i];
}
//发送异步请求
xhr.open("get",url,true);
xhr.send(null);
}
/**
* 处理响应数据
* @param data 响应数据
* @param dealType 响应数据处理对象
* @param dom 事件源
*/
var dealData = function(data,dealType,dom){
var dataType = Object.prototype.toString.call(data);
switch (dealType) {
case "sug":
if (dataType === "[object Array]") {
//创建提示按钮
return createSug(data,dom);
}
if (dataType === "[object Object]") {
var newData = [];
for(var i in data){
newData.push(data[i]);
}
//提示创建按钮组件
return createSug(newData,dom);
}
return createSug([data],dom);
break;
case 'validate':
//创建校验组件
return createValidataResult(data,dom);
break;
}
}
/**
* 创建提示框组件
* @param data 响应适配数据
* @param dom 事件源
*/
var createSug = function(data,dom){
var i = 0;
var len = data.length;
var html = "";
for(;i<len;i++){
html += '<li>'+data[i]+'</li>';
}
dom.parentNode.getElementsByTagName("ul")[0].innerHTML = html
}
/**
* 创建校验组件
* @param data 响应适配数据
* @param dom 事件源
*/
var createValidataResult = function(data,dom){
//显示验证结果
dom.parentNode.getElementsByTagName("span")[0].innerHTML = data;
}
//单元测试
var createSug = function (data, dom) {
console.log(data, dom, "createSug");
}
var createValidataResult = function (data, dom) {
console.log(data, dom, "createValidataResult");
}
<body>
<input type="text">
<input type="submit">
<span></span>
<script src="js/utils.js"></script>
<script>
var input = document.getElementsByTagName("input");
input[0].onchange = function(e){
sendData({value:input[0].value},"validate",input[0]);
}
input[1].onkeydown = function(e){
sendData({value:input[1].value},"sug",input[1])
}
</script>
</body>
命令模式
把请求和实现解耦并且封装成独立对象,让不同的请求对客户端的实现参数化。
/**
* 动态创建视图
* 命令模式
*/
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("")
};
var html = '';
function formateString(str,obj){
//替换{##}之间的字符串
return str.replace(/\{#(\w+)#\}/g,function(match,key){
return obj[key]
})
}
//方法集合
var Action = {
create:function(data,view){
if (data.length) {
for(var i = 0,len=data.length;i<len;i++){
html += formateString(tpl[view],data[i])
}
}else{
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不是数组,则将其转化为数组,因为apply方法要求第二个参数为数组
msg.param = Object.prototype.toString.call(msg.param) === "[object Array]" ? msg.param : [msg.param];
//Action内部调用的方法引用this,确保作用域的this传入Action中
Action[msg.command].apply(Action,msg.param);
}
})();
访问者模式(Visitor)
针对对象结构中的元素,定义一个在不修改该对象的前提下访问结构中的方法。
如下:
/**
* 访问者模式
*/
var Visitor = (function(){
return {
// 截取方法
splice:function(){
var args = Array.prototype.splice.call(arguments,1);
return Array.prototype.splice.apply(arguments[0],args);
},
//追加数据的方法
push:function(){
var len = arguments[0].length || 0;
//从原参数的第二个元素开始添加
var args = this.splice(arguments,1);
arguments[0].length = len + arguments.length - 1;
return Array.prototype.push.apply(arguments[0],args);
},
pop:function(){
return Array.prototype.pop.apply(arguments[0]);
}
}
})();
var s = new Object();
Visitor.push(s,1,3,8,4)
console.log(s.length);
中介者模式
通过中中介者对象封装呀一系列对象之间的交互,让对象之间不再是相互引用,降低他们之间的耦合。
如下:
/**
* 中介者模式
*/
var Mediator = function(){
//消息对象
var _msg = {};
return {
/**
* 订阅消息的方法
* @param type 消息名称
* @param action 消息回调函数
*/
register:function(type,action){
//检测该消息是否存在,若在则直接存入回调函数;若不存在该消息,则新建一个容器,再存放入回调函数中
if (_msg[type]) {
_msg[type].push(action);
}else{
_msg[type] = [];
_msg[type].push(action);
}
},
/**
* 发布信息
* @param type 消息名称
*/
send:function(type){
//若已经被订阅了
if (_msg[type]) {
for(var i =0,len = _msg[type].length;i<len;i++){
_msg[type][i] && _msg[type][i]();
}
}
}
}
}();
Mediator.register("dome",function(){
console.log(156484)
});
Mediator.send("dome");
/**
* 显示隐藏导航组件
* @param mod 模块
* @param tag 操作的标签
* @param showOrHide 显示或者隐藏
*/
var showHideNavWidget = function(mod,tag,showOrHide){
var mod = document.getElementById(mod);
var tag = mod.getElementsByTagName(tag);
var showOrHide = (!showOrHide || showOrHide === 'hide') ? "hidden" : "visible";
//隐藏这些标签,但是依旧占据位置
for(var i=tag.length-1;i>=0;i--){
tag.style.visibility = showOrHide;
}
};
/**
* test
*/
(function(){
//隐藏
Mediator.register('hideAllNavNum',function(){
showHideNavWidget("collection_nav",'b',false);
});
//显示
Mediator.register("showAllNavNum",function(){
showHideNavWidget("collection_nav",'b',true);
})
})();
备忘录模式(Memento)
在不破坏对象的封装性前提下,在该对象之外捕获并且保存该对象内部状态,方便以后对象使用或者对象恢复之前的某个状态。
如下:
/**
* 备忘录模式
* 缓存数据
*/
var Page = function(){
//信息缓存
var cache = {};
/**
* @param page 页码
* @param fn 成功回调函数
*/
return function(page,fn){
//判断该页数据是否在缓存中
if (cache[page]) {
showPage(page,cache[page]);
fn && fn();
}else{
//不存在则请求数据
$.post('./data/getdata.php',{
page:page
},function(res){
if (res.errNo == 0) {
showPage(page,res.data);
cache[page] = res.data;
fn && fn();
}else{
}
})
}
}
}();
//test 点击下一页
$("$next_page").click(function(){
var news = $("#news_content"),
page = $news.data("page");
Page(page,function(){
$news.data("page",page+1);
})
});
备忘录模式是对现有的数据或者状态做缓存,以便将来恢复做准备。JavaScript中的备忘录模式是对数据进行缓存备份。当数据量过大时,会严重占用系统提供的资源,因此,复用率较低的数据不必缓存备份!
迭代器模式
在不暴露对象内部结构的同时,可以顺序的访问聚合对象的内部元素
/**
* 迭代器模式中的焦点轮播图
*/
//迭代器
var Iterator = function(items,container){
//获取父容器
var container = container && document.getElementById(container) || document;
var items = container.getElementsByTagName(items);
var length = items.length;
var index = 0;
var splice=[].splice();
return {
first:function(){
//获取第一个元素
index = 0;
return items[index];
},
last: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;
},
//对每一个元素执行某一种方法
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(argsuments,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);
}
}
}
}
var dom = new Iterator('li','container');
console.log(dom.first());
/**
* 数组迭代器
*/
var eachArray = function(arr,fn){
var i = 0 ;
var len = arr.length;
for(;i<len;i++){
if(fn.call(arr[i],i,arr[i]) === false){
breack;
}
}
}
/**
* 对象迭代器
*/
var eachObject = function(obj,fn){
for(var i in obj){
if(fn.call(obj[i],i,obj[i])=== false){
breack;
}
}
}
/**
* 同步变量迭代器
*/
var A ={
common:{},
client:{
user:{
username:"读心",
uid:"123"
}
},
server:{}
}
var 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.uid')); // 123
//设置赋值
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;
}
console.log(Asetter("client.module.news.sports","on")); //on
解释器模式(Interpreter)
对于一种语言,给出它的文法表示形式,并且定义一种解释器,通过解释器来解释语言中定义的句子
例如要统计页面中某个元素的所处在的路径:
/**
* 解释器模式
* 获取兄弟元素
*/
var Interpreter = (function(){
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.nodeType) {
//
if (nodeName == sibling.nodeName) {
name += ++count;
} else {
count = 1;
name += "|" + sibling.nodeName.toUpperCase();
}
}
sibling = sibling.previousSibling;
}
return name;
} else {
return "";
}
}
/**
* @param node 目标节点
* @param wrap 容器节点
*/
return function(node,wrap){
var path = [];
var 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 sublingName = getSublingName(node);
//节点为元素
if (node.nodeType == 1) {
//当前节点元素名称以及前面的兄弟元素名称统计
path.push(node.nodeName.toUpperCase() + sublingName);
}
return path;
}
})();