設計模式簡介
設計模式原則
- 單一職責原則: 一個對象(方法)只做一件事. 優點是降低了單個類或者對象的復雜度,
按職責吧對象分界成更小的粒度,有助于代碼的復用.當一個職責需要變更的時候,不會影響到其他職責
- 最少只是原則: 盡量減少對象之間的交互. 一個模塊或者對象可以將內部的數據或實現細節隱藏起來,
只暴露必要的接口API供外界訪問.
- 開放-封閉原則: 軟件實體(類, 模塊, 函數)等應該是可以擴展的, 但是不可修改. 將容易被修改的代碼
和不容易被修改的代碼分開.各自封裝.
代碼重構
- 提煉函數: 如果在函數中有一段代碼可以被獨立出來, 我們最好把這些代碼放進另外一個獨立的函數中.
可以避免出現超大函數, 有利于代碼的復用, 獨立出來的函數更容易被修改.
- 合并重復的條件片段: 如果一個函數體內有一些條件分支語句,而這些條件分支語句內部散步了一些重復的
代碼,那么就需要進行合并去重.
-
把條件分支語句提煉成函數.或提前賦值給一個變量.
-
合理使用循環.
-
提前讓函數退出代替嵌套條件分支: 如果有一個條件分支是直接退出函數, 那么把退出操作放在前面,
而不是把他else里面.
-
傳遞對象參數代替過長的參數列表.
-
盡量較少參數數量.
-
不要用過于復雜的三次運算符.
-
合理使用鏈式調用.
-
分界大型類.
閉包和高階函數
使用閉包做私有變量
將多個全局變量轉化成私有變量,用一個全局對象提供的接口去修改,獲取這些變量.統一處理
var user = (function() {
var _name = "sven";
var _age = 28;
return {
getUserInfo: function() {
return _name + "-" + _age;
}
}
})()
判斷類型的方法
//類型判斷方法
var Type = (function() {
var Type = {}
var typeArr = ["String", "Array", "Number", "Undefined", "Function", "Boolean", "Null", "Object"]
typeArr.forEach(function(item) {
(function(item) {
Type["is" + item] = function(obj) {
return Object.prototype.toString.call(obj) === '[object ' + item + ']';
}
})(item)
})
return Type;
})()
用閉包做函數緩存
把函數的計算結果保存起來, 如果參數相同.則直接把對應結果返回;
var mult = (function() {
var cache = {}
return function() {
var args = [].join.bind(arguments)(",");
if (cache[args]) {
return chche[args]
}
var a = 1;
[].forEach.bind(arguments)(function(item) {
a = a * item;
})
cache[args] = a;
return a;
}
})()
切面編程
//切面編程;在函數執行前, 執行后發送日志;
Function.prototype.before = function(beforeFn) {
var _self = this;
return function() {
beforeFn.apply(this, arguments);
return _self.apply(this, arguments);
}
}
Function.prototype.after = function(afterFn) {
var _self = this;
return function() {
var ret = _self.apply(this, arguments);
afterFn.apply(this, arguments);
return ret;
}
}
var func = function() {
log(2)
}
func = func.before(function() {
log(1)
}).after(function() {
log(3)
})
func();
函數節流
限制函數的最大執行頻率,比如500ms, 如果在小于500ms時再次觸發了函數,就把原來的函數清理掉.
再往后延遲500ms;依次來降低函數的觸發頻率;
var throttle = function(fn, config) {
config = config || {};
var objConfig = {
interval: config.interval || 500,
firstTime: config.firstTime || true,
}
var timer;
return function() {
var args = arguments;
var self = this;
if (objConfig.firstTime) {
fn.apply(self, args);
objConfig.firstTime = false;
return
}
if (timer) {
clearTimeout(timer)
timer = window.setTimeout(function() {
clearTimeout(timer)
timer = null;
fn.apply(self, args);
}, objConfig.interval)
return false;
}
timer = window.setTimeout(function() {
clearTimeout(timer)
timer = null;
fn.apply(self, args);
}, objConfig.interval)
}
}
分時函數
一個1000個元素的數組, 每個元素都要執行一個函數.直接執行會卡頓.
所以1000個元素分批次, 比如200毫秒執行10個. 以此來保證程序能正常運行;
var timeChunk = function(ary, fn, count) {
var obj,
t;
var len = ary.length;
var start = function() {
var max = Math.min(count || 1 , len)
for (var i = max; i >= 0; i--) {
var obj = ary.shift();
fn(obj);
}
}
return function() {
t = setInterval(function() {
if (ary.length === 0) {
return clearInterval(t)
}
start();
}, 1000)
}
}
var a = [1]
a.length = 1000;
var l = function() {
console.log(1)
}
var d = timeChunk(a, l, 10);
在原有代碼上安全添加功能
Function.prototype.after = function(afterfn) {
var _self = this;
return function() {
var ret = _self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
};
//在原來的window.onload基礎上添加一些功能;
window.onload = (window.onload || function() {}).after(function() {
//要添加的功能
console.log(document.getElementsByTagName("*").length)
})
單例模式
單例通用模板;其實就是一個緩存的問題.這個函數的目的是生成一個對象.
用res作為函數的私有變量來預計保存這個對象. 如果原來沒有. 就執行創建.
并把這個對象保存在私有變量res上. 如果有了. 那就直接把這個res返回即可;
var getSingle = function(fn) {
var result;
return function() {
return result || (result = fn.apply(this, arguments))
}
}
var createLoginLayer = function(){
var div = document.createElement( 'div' );
div.innerHTML = '我是登錄浮窗';
div.style.display = 'none';
document.body.appendChild( div );
return div;
};
var createSingleLoginLayer = getSingle(createSingleLoginLayer);
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
代理模式
我們需要調用某個對象的接口b, 但是調用的時機是另一個邏輯. 我們把調用的時機這套邏輯放到一個函數a中.
通過調用a來調用b,這就是代理模式. 代理模式有個原則: 原接口和代理的一致性原則. 即代理的使用方式和原接口的使用方式是一樣的;
圖片代理
var myImage = (function() {
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})()
var proxyImage = (function() {
var img = new Image()
img.onload = function() {
myImage.setSrc(this.src);
}
return {
setSrc: function(src) {
myImage.setSrc("loading.png");
img.src = src;
}
}
})()
proxyImage.setSrc("login.png")
虛擬代理合并HTTP請求
比如有一個列表;點擊之后發送請求,上傳對應的文件.一秒點擊三次的話,就會發送三次請求. 我們可以收集2秒之內的請求. 然后統一發送;
var synchronousFile = function(id) {
console.log("開始同步文件" + id);
}
var proxySynchronousFile = function() {
var cache = [],
timer;
return function(id) {
cache.push(id);
if (timer) {
return;
}
timer = setTimeout(function() {
synchronousFile(cache.join(","));
clearTimeout(timer)
timer = null;
chche.length = 0;
}, 2000)
}
}
var dom = document.querySelectorAll("input")
Array.forEach.bind(dom)(function(item) {
item.onclick = function() {
proxySynchronousFile(this.id);
}
})
高階函數動態創建緩存代理
var createProxyFactory = function(fn) {
var cache = {};
return function() {
var args = Array.prototype.join.call(arguments, ",");
if (args in cache) {
return cache[args];
}
return cache[args] = fn.apply(this, arguments);
}
}
var mult = function() {
//計算參數的乘積;
var a = 1;
Array.forEach.bind(arguments)(function(item) {
a *= item;
})
return a;
}
var proxyMult1 = createProxyFactory(mult);
發布訂閱者模式
var event1 = {
clientList: {},
listen: function(key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function() {
var key = Array.prototype.shift.call(arguments),
fns = this.clientList[key];
if (!fns || fns.length === 0) {
this.clientList[key + "Offline"] = []
return false;
}
for (var i = fns.length - 1; i >= 0; i--) {
var fn = fns[i]
fn.apply(this, arguments)
};
},
remove: function(key, fn) {
var fns = this.clientList[key]
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
[].forEach.bind(fns)(function(itemFn, index) {
if (itemFn === fn) {
fns.splice(index, 1);
}
})
}
}
};
命令模式
游戲重播
var log = console.log.bind(console)
var Ryu = {
attack: function() {
log("攻擊")
},
defense: function() {
log("防御")
},
jump: function() {
log("跳躍")
},
crouch: function() {
log("蹲下")
}
}
//返回對應的命令;
var makeCommand = function(receiver, state) {
return function() {
receiver[state]()
}
}
//keyCode和函數對照表;
var commands = {
"119": "jump", //w
"115": "crouch", //s
"97": "defense", //A
"100": "attack", //D
}
//命令存儲;
var commandStack = [];
//當點擊按鍵時;
document.onkeypress = function(ev) {
var keyCode = ev.keyCode,
command = makeCommand(Ryu, commands[keyCode])
if (command) {
command()
commandStack.push(keyCode);
}
};
//點擊重播
document.getElementById("replay").onclick = function() {
var command;
while(command = commandStack.shift()) {
Ryu[commands[command]]()
}
}
異步命令隊列
var log = console.log.bind(console)
var Ryu = {
attack: function() {
setTimeout(function() {
log("攻擊")
commandStack.next();
}, 1000)
},
defense: function() {
setTimeout(function() {
log("防御")
commandStack.next();
}, 1000)
},
jump: function() {
setTimeout(function() {
log("跳躍")
commandStack.next();
}, 1000)
},
crouch: function() {
setTimeout(function() {
log("蹲下")
commandStack.next();
}, 1000)
}
}
//返回對應的命令;
var makeCommand = function(receiver, state) {
return function() {
receiver[state]()
}
}
//keyCode和函數對照表;
var commands = {
"119": "jump", //w
"115": "crouch", //s
"97": "defense", //A
"100": "attack", //D
}
//動畫隊列;
var commandStack = {
state: false, //是否正在運行命令隊列中;
list: [],
add: function(commond) {
this.list.push(commond);
},
next: function() {
var func = this.list.shift()
if (func) {
this.state = true;
func()
} else {
this.state = false;
}
},
start: function() {
if (!this.state) {
this.next()
}
}
};
//當點擊按鍵時;
document.onkeypress = function(ev) {
var keyCode = ev.keyCode,
command = makeCommand(Ryu, commands[keyCode])
if (command) {
commandStack.add(command);
commandStack.start()
}
};
中介者模式
在邏輯應用中, 常常有多對多的對應關系.過個對象互相影響, 一個對象發生改變,會影響多個對象的狀態.
比如排行榜頁面的三級聯動和分頁排序關系. 三級聯動的每一級的修改都可能影響排序的集合和狀態以及列表的狀態.
這就是多對多的關系. 如果要在每一個值發生改變時造成的影響,都寫在各自的邏輯中,代碼就會顯得非常的雜亂,
并且難以修改. 這時就適合用中介者模式, 添加一個中介者.把多對多的關系變成多對一的關系. 多個對象對一個中介者.
下面是一個例子. 多人游戲泡泡堂.
兩個隊, 8個人的游戲, 每一個人的添加, 死亡, 換隊.都有可能影響其他人的狀態, 并影響最終的勝負.
var log = console.log.bind(console);
var Player = function(name, teamColor) {
this.name = name;
this.teamColor = teamColor;
this.state = "alive";
}
Player.prototype.win = function() {
log(this.name + "win");
}
Player.prototype.lose = function() {
log(this.name + "lose")
}
Player.prototype.die = function() {
this.state = "dead";
playerDirector.reciveMessage("playerDead", this);
}
Player.prototype.remove = function() {
playerDirector.reciveMessage("removePlayer", this);
}
Player.prototype.changeTeam = function(color) {
playerDirector.reciveMessage("changeTeam", this, color);
}
var playerFactory = function(name, teamColor) {
var newPlayer = new Player(name, teamColor);
playerDirector.reciveMessage("addPlayer", newPlayer);
return newPlayer;
}
//隊伍管理器
var playerDirector = (function() {
var players = {}; //保存所有的玩家;
var operations = {}; //中介者可以執行的操作
//新增;
operations.addPlayer = function(player) {
var teamColor = player.teamColor;
players[teamColor] = players[teamColor] || [];
players[teamColor].push(palyer);
};
//移除
operations.removePlayer = function(palyer) {
var teamColor = player.teamColor;
var teamPlayers = palyers[teamColor] || [];
teamPlayers.forEach(function(item, index) {
if (item === player) {
teamColor.splice(i, 1);
}
})
}
//玩家換隊
operations.changeTeam = function(palyer, newTeamColor) {
operations.removePlayer(player);
player.teamColor = newTeamColor;
operations.addPlayer(player);
}
//玩家死亡;
operations.playerDead = function(player) {}
var reciveMessage = function() {
var message = Array.prototype.shift.call(arguments);
operations[message].apply(this, arguments);
}
return {
reciveMessage: reciveMessage,
}
})()
職責鏈模式
職責鏈模式處理的是多個同級狀態的復雜邏輯問題.
用職責鏈模式需要符合以下幾個條件:
-
某個元素或對象有多種狀態
-
這些狀態是統一等級的
-
每一個狀態對應的邏輯都比較復雜, 如果用if-else來寫會很繁雜
-
將來有可能會添加一些狀態,或修改邏輯判斷的順序
在此情況下, 比較適合職責鏈模式. 因為元素有多種狀態, 當元素來臨時, 不知道用哪個函數邏輯處理
此時直接拋給第一個函數鏈, 他會自動檢測是否可以處理, 如果不能處理就會拋給下一個函數鏈. 直到能處理為止
解耦了請求發送者和n個接受者之間的復雜關系.
最大的優勢是如果添加/減少狀態或修改了狀態的順序. 可以很方便的修改函數鏈.
在原型鏈/作用域鏈/事件冒泡等機制中,都能找到職責鏈模式的影子. 都是一個請求發過來, 先看當前對象能否處理,
不能的話就傳到下一個鏈條, 直到處理完為止.
職責鏈經典模式
//同步函數: 下一步用 return "nextSuccessor"
//異步函數: 下一步用self.next()
var Chain = function(fn) {
this.fn = fn;
this.successor = null;
}
Chain.prototype.setNextSuccessor = function(successor) {
return this.successor = successor
}
Chain.prototype.passRequest = function() {
var ret = this.fn.apply(this, arguments)
if (ret === "nextSuccessor") {
return this.successor && this.successor.passRequest.apply(this.successor, arguments)
}
return ret;
}
Chain.prototype.next = function() {
return this.successor && this.successor.passRequest.apply(this.successor, arguments)
}
//同步函數: 下一步用 return "nextSuccessor"
var fn1 = new Chain(function() {
console.log("1")
return "nextSuccessor"
})
//異步函數: 下一步用self.next()
var fn2 = new Chain(function() {
console.log(2)
var self = this;
setTimeout(function() {
self.next()
}, 1000)
})
var fn3 = new Chain(function() {
console.log(3)
})
//將函數鏈連接起來;
fn1.setNextSuccessor(fn2).setNextSuccessor(fn3)
//開始處理狀態數據
chainOrder500.passRequest(1, true, 500);
用AOP切面編程實現職責鏈
Function.prototype.after = function(fn) {
var self = this;
return function() {
var ret = self.apply(this, arguments)
if (ret === "nextSuccessor") {
return fn.apply(this, arguments)
}
return ret;
}
}
fn1.after(fn2).after(fn3)
浙公網安備 33010602011771號