學習設計模式筆記
設計模式簡介
設計模式原則
- 單一職責原則: 一個物件(方法)只做一件事. 優點是降低了單個類或者物件的複雜度,
按職責吧物件分界成更小的粒度,有助於程式碼的複用.當一個職責需要變更的時候,不會影響到其他職責
- 最少只是原則: 儘量減少物件之間的互動. 一個模組或者物件可以將內部的資料或實現細節隱藏起來,
只暴露必要的介面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)