行為設計模式用于不同對象之間職責劃分、算法抽象,包含:模闆方法模式、觀察者模式、狀态模式、政策模式、職責鍊模式、指令模式、通路者模式、中介模式、備忘錄模式、疊代器模式、解釋器模式。
模闆方法模式(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;
}
})();