设计模式简介
设计模式原则
- 单一职责原则: 一个对象(方法)只做一件事. 优点是降低了单个类或者对象的复杂度,
按职责吧对象分界成更小的粒度,有助于代码的复用.当一个职责需要变更的时候,不会影响到其他职责
- 最少只是原则: 尽量减少对象之间的交互. 一个模块或者对象可以将内部的数据或实现细节隐藏起来,
只暴露必要的接口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)