一、单例模式
有些对象我们只需要一个,比如线程池、全局缓存、浏览器中的window对象、登录框、购物车、JQuery的$,vuex和redux等
单例模式创建登录框,创建对象和管理单例的职责被分布在不同的方法中,这两个方法组合起来才具有单例模式的威力。
1、创建登录框
const getSinge = function(fn){
var result;
return function(){
if(result){ return result };
result = fn.apply(this, arguments);
return result;
}
}
const createLoginLayer = function(name){
var div = document.createElement('div');
div.innerHTML = name;
div.style.display = 'none';
document.body.append(div);
return div;
}
const createSingeLoginLayer = getSinge(createLoginLayer);
document.getElementById('loginBtn').onclick = function(){ // 页面只会有一个div
var loginLayer = createSingeLoginLayer('chenshun Login');
loginLayer.style.display = 'block';
}
二、策略模式
1.策略模式重构表单校验
<!DOCTYPE html><html>
<body>
<form action="http://xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName" />
请输入密码:<input type="text" name="password" />
请输入手机号码:<input type="text" name="phoneNumber" />
<button>提交</button>
</form>
<script>
var strategies = {
isNonEmpty: function(value, errorMsg){
if(value === ''){
return errorMsg;
}
},
minLength: function(value, length, errorMsg){
if(value.length < length){
return errorMsg;
}
},
isMobile: function(value, errorMsg){
if(!/(^1[3|5|8][0-9]{9}$)/.test(value)){
return errorMsg;
}
}
}
var Validator = function(){
this.cache = [];
};
Validator.prototype.add = function(dom, rules){
var self = this;
for(var i = 0, rule; rule = rules[i++];){
(function(rule){
var strategyAry = rule.strategy.split(':');
var errorMsg = rule.errorMsg;
self.cache.push(function(){
var strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategies[strategy].apply(dom, strategyAry);
});
})(rule)
}
};
Validator.prototype.start = function(){
for(var i = 0, validatorFunc; validatorFunc = this.cache[i++];){
var errorMsg = validatorFunc();
if(errorMsg){
return errorMsg;
}
}
};
var registerForm = document.getElementById('registerForm');
var validataFunc = function(){
var validator = new Validator();
validator.add(registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:10',
errorMsg: '用户名长度不能小于10位'
}]);
validator.add(registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密码长度不能小于6位'
}])
validator.add(registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手机号格式不正确'
}])
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function(event){
event.preventDefault();
var errorMsg = validataFunc();
if(errorMsg){
alert(errorMsg);
return false;
}
}
</script>
</body>
</html>
三、代理模式
1.代理实现图片预加载
<!DOCTYPE html>
<html>
<body>
<script>
var myImage = (function(){
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return function(src){
imgNode.src = src;
}
})();
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage(this.src); // 回调函数(非箭头函数回调)中this指向监听的对象
}
return function(src){
myImage('https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3919534254,824588318&fm=26&gp=0.jpg');
img.src = src;
}
})();
proxyImage('https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2454599664,1105030419&fm=26&gp=0.jpg');
// 必要时可以直接去掉代理
myImage('https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2454599664,1105030419&fm=26&gp=0.jpg');
</script>
</body>
</html>
2.代理文件上传,频繁点击限制每两秒才能上传一次
<!DOCTYPE html>
<html>
<body>
<input type="checkbox" id="1"></input>1
<input type="checkbox" id="2"></input>2
<input type="checkbox" id="3"></input>3
<input type="checkbox" id="4"></input>4
<input type="checkbox" id="5"></input>5
<script>
var synchronousFile = function(id){
console.log(`开始同步文件,id为${id}`)
};
var proxySynchronousFile = (function(){
const cache = [];
let timer;
return function(id){
cache.push(id);
if(timer){ return; };
timer = setTimeout(()=>{
synchronousFile(cache.join(','));
clearTimeout(timer);
timer = null;
cache.length = 0;
}, 2000)
}
})()
var checkbox = document.getElementsByTagName('input');
for(var i = 0, c; c = checkbox[i++];){
c.onclick = function(){
if(this.checked === true){
proxySynchronousFile(this.id);
}
}
}
</script>
</body>
</html>
3.缓存代理,计算乘积
var mult = function(){
console.log('开始计算乘积');
var a = 1; for( var i = 0, l = arguments.length; i < l ; i++){
a = a * arguments[i];
}
return a;
}
var proxyMult = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call(arguments, ',');
if(args in cache){
return cache[args];
}
return cache[ args ] = mult.apply(this, arguments);
}})();
console.log(proxyMult(1,2,3,4));
console.log(proxyMult(1,2,3,4));
4.高阶函数动态创建代理
const mult = function(){
console.log('乘积');
let a = 1;
for( let i = 0, l = arguments.length; i < l ; i++){
a = a * arguments[i];
}
return a;
}
const plus = function(){
console.log('和');
let a = 0;
for(let i = 0, l = arguments.length; i < l; i++){
a = a + arguments[i];
}
return a;
}
const createProxyFactory = function(fn){
const cache = {};
return function(){
const args = Array.prototype.join.call(arguments, ',');
if(args in cache){
return cache[args];
}
return cache[ args ] = fn.apply(this, arguments);
}
};
const proxyMult = createProxyFactory(mult);
const proxyPlus = createProxyFactory(plus);
console.log(proxyMult(1,2,3,4));
console.log(proxyMult(1,2,3,4));
console.log(proxyPlus(1,2,3,4));
console.log(proxyPlus(1,2,3,4));
四、发布-订阅模式
1.支持先发布后订阅,并可以给事件对象提供创建命名空间的功能
const Event = (function(){
var Event,
_default = 'default';
Event = function(){
var _listen,
_trigger,
_remove,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
each = function(ary, fn){
var ret;
for(var i = 0, l = ary.length; i < l; i++){
var n = ary[i];
ret = fn.call(n, i, n);
}
return ret;
};
_listen = function(key, fn, cache){
if(!cache[key]){
cache[key] = [];
}
cache[key].push(fn);
};
_remove = function(key, cache, fn){
if(cache[key]){
if(fn){
for(var i = cache[key].length; i >= 0; i--){
if(cache[key][i] === fn){
cache[key].splice(i, 1);
}
}
}else{
cache[key] = [];
}
}
};
_trigger = function(){
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key];
if(!stack || !stack.length){
return;
}
return each(stack, function(){
return this.apply(_self, args);
});
};
_create = function(namespace){
var namespace = namespace || _default;
var cache = {},
offlineStack = [], //离线事件
ret = {
listen: function(key, fn, last){
_listen(key, fn, cache);
if(offlineStack === null){
return
}
if(last === 'last'){
offlineStack.length && offlineStack.pop();
}else{
each(offlineStack, function(){
this();
})
}
offlineStack = null;
},
one: function(key, fn, last){
_remove(key, cache);
this.listen(key, fn, last);
},
remove: function(key, fn){
_remove(key, cache, fn);
},
trigger: function(){
var fn,
args,
_self = this;
_unshift.call(arguments, cache);
args = arguments;
fn = function(){
return _trigger.apply(_self, args);
};
if(offlineStack){
return offlineStack.push(fn);
}
return fn();
}
};
return namespace ?
(namespaceCache[namespace] ? namespaceCache[namespace] :
namespaceCache[namespace] = ret)
: ret;
};
return {
create: _create,
one: function(key, fn, last){
var event = this.create();
event.one(key, fn, last);
},
remove: function(key, fn){
var event = this.create();
event.remove(key, fn);
},
listen: function(key, fn, last){
var event = this.create();
event.listen(key, fn, last);
},
trigger: function(){
var event = this.create();
event.trigger.apply(this, arguments);
}
}
}()
return Event;
})()
Event.trigger('click', 1);
Event.listen('click', function(a){
console.log(a);
});
Event.listen('click', function(a){
console.log(a); // 监听两次只打印一次1
});
Event.create('namespace1').trigger('click', 1);
Event.create('namespace1').listen('click', function(a){
console.log(a);
})
Event.create('namespace2').trigger('click', 2);
Event.create('namespace2').listen('click', function(a){
console.log(a);
})
五、命令模式
JavaScript可以用高阶函数非常方便地实现命令模式,命令模式在JavaScript语言中是一种隐形的模式。点击一个按钮执行一个特点或一系列操作就是一种命令模式,以下用命令模式模拟街头霸王游戏,支持回放
<!DOCTYPE html>
<html>
<body>
<button id="replay">播放录像</button>
<script>
var Ryu = {
attack: function(){
console.log('攻击');
},
defense: function(){
console.log('防御');
},
jump: function(){
console.log('跳跃')
},
crouch: function(){
console.log('蹲下')
}
};
var makeCommand = function(receiver, state){
if(receiver[state]){
return function(){
receiver[state]();
}
}
};
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(command); // 将刚刚执行过的命令保存进堆栈
}
};
document.getElementById('replay').onclick = function(){ // 点击播放录像
var command;
while(command = commandStack.shift()){
command();
}
};
</script>
</body>
</html>
六、组合模式
基本对象可以被组合成更复杂的组合对象,组合对象又可以被组合,这样不断传递下去
1.组合模式打开家电
<!DOCTYPE html>
<html>
<body>
<button id="button">按我</button>
<script>
var MacroCommand = function(){
return {
commandList: [],
add: function(command){
this.commandList.push(command);
},
excute: function(){
for(var i = 0, command; command = this.commandList[i++];){
command.excute();
}
}
}
};
var openAcCommand = {
excute: function(){
console.log('打开空调');
}
};
/* 家里空调和音响是连在一起的,所以可以用一个宏命令来组合打开电视和打开音响的命令 */
var openTvCommand = {
excute: function(){
console.log('打开电视');
}
};
var openSoundCommand = {
excute: function(){
console.log('打开音响');
}
};
var macroCommand1 = MacroCommand();
macroCommand1.add(openTvCommand);
macroCommand1.add(openSoundCommand);
/* 关门、打开电脑和登录qq的命令*/
var closeDoorCommand = {
excute: function(){
console.log('关门');
}
};
var openPcCommand = {
excute: function(){
console.log('打开电脑');
}
};
var openQQCommand = {
excute: function(){
console.log('登录QQ');
}
};
var macroCommand2 = MacroCommand();
macroCommand2.add(closeDoorCommand);
macroCommand2.add(openPcCommand);
macroCommand2.add(openQQCommand);
/* 现在把所有的命令组合成一个超级命令 */
var macroCommand = MacroCommand();
macroCommand.add(openAcCommand);
macroCommand.add(macroCommand1);
macroCommand1.add(macroCommand2);
/* 最后给遥控器绑定超级命令 */
var setCommand = (function(command){
document.getElementById('button').onclick = function(){
command.excute();
}
})(macroCommand);
</script>
</body>
</html>
2.组合模式实现扫描文件夹
/** * Folder */
var Folder = function(name){
this.name = name;
this.files = [];
};
Folder.prototype.add = function(file){
this.files.push(file);
};
Folder.prototype.scan = function(){
console.log(`开始扫描文件夹:${this.name}`);
for(var i = 0, file, files = this.files; file = files[i++];){
file.scan();
}
};
/** * File */
var File = function(name){
this.name = name;
};
File.prototype.add = function(){
throw new Error('文件下面不能再添加文件');
};
File.prototype.scan = function(){
console.log(`开始扫描文件:${this.name}`);
};
var folder = new Folder('学习资料');
var folder1 = new Folder('JavaScript');
var folder2 = new Folder('jQuery');
var file1 = new File('JavaScript设计模式与开发实践');
var file2 = new File('精通jQuery');
var file3 = new File('重构与模式');
folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3);
var folder3 = new Folder('Nodejs');
var file4 = new File('深入浅出Node.js');
folder3.add(file4);
var file5 = new File('JavaScript语言精髓与编程实践');
folder.add(folder3);
folder.add(file5);
folder.scan();
3.扫描文件夹之前可以先删除
/** * Folder */
var Folder = function(name){
this.name = name;
this.parent = null;
this.files = [];
};
Folder.prototype.add = function(file){
file.parent = this;
this.files.push(file);
};
Folder.prototype.scan = function(){
console.log(`开始扫描文件夹:${this.name}`);
for(var i = 0, file, files = this.files; file = files[i++];){
file.scan();
}
};
Folder.prototype.remove = function(){
if(!this.parent){ // 根节点或者树外的游离节点
return
}
for(var files = this.parent.files, l = files.length - 1; l >=0; l--){
var file = files[l];
if(file === this){
files.splice(l, 1);
}
}
}
/** * File */
var File = function(name){
this.parent = null;
this.name = name;
};
File.prototype.add = function(){
throw new Error('文件下面不能再添加文件');
};
File.prototype.scan = function(){
console.log(`开始扫描文件:${this.name}`);
};
File.prototype.remove = function(){
if(!this.parent){
// 根节点或者树外的节点
return;
}
for(var files = this.parent.files, l = files.length - 1; l >=0; l--){
var file = files[l];
if(file === this){
files.splice(l, 1);
}
}
}
var folder = new Folder('学习资料');
var folder1 = new Folder('JavaScript');
var file1 = new Folder('深入浅出Node.js');
folder1.add(new File('JavaScript设计模式与开发实践'));
folder.add(folder1);folder.add(file1);
folder1.remove();
folder.scan();
七、模版方法模式
在模版方法中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来实现。在JavaScript中,我们很多时候都不需要这样照壶画瓢地去实现一个模版方法模式,高阶函数是更好的选择。
1.以冲泡饮料为例,泡茶和泡咖啡很多过程都是相识的,可以用模版方法模式来搭建冲泡骨架
var Beverage = function(param){
var boilWater = function(){
console.log('把水沸腾');
};
var brew = param.brew || function(){
throw new Error('必须传递brew方法');
};
var pourInCup = param.pourInCup || function(){
throw new Error('必须传递pourInCup方法')
};
var addCondiments = param.addCondiments || function(){
throw new Error('必须传递addCondiments方法');
}
var F = function(){};
F.prototype.init = function(){
boilWater();
brew();
pourInCup();
addCondiments();
};
return F;
};
var Coffee = Beverage({
brew: function(){
console.log('用沸水冲泡咖啡');
},
pourInCup: function(){
console.log('把咖啡倒进被子');
},
addCondiments: function(){
console.log('加糖和牛奶');
}
});
var Tea = Beverage({
brew: function(){
console.log('用沸水浸泡茶叶');
},
pourInCup: function(){
console.log('把茶倒进杯子');
},
addCondiments: function(){
console.log('加柠檬');
}
});
var coffee = new Coffee();
coffee.init();
var tea = new Tea();
tea.init();
八、享元(flyweight)模式
享元模式是为了解决性能问题而生的一种模式,这跟大部分模式的诞生原因都不一样。在一个存在大量相似对象的系统中,享元模式可以很好的解决大量对象带来的性能问题。
剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系统中的对象数量,相比之下,这点时间或许微不足道。因此,享元模式是一种用时间换空间的优化方式。
享元模式适用场景:1、一个程序中适用了大量的相似对象。2、由于使用了大量对象,造成很大的内存开销。3、对象的大多数状态都可以变为外部状态。4、剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量的对象。
1.文件上传大量创建插件上传对象和flash上传对象导致对象爆炸
<!DOCTYPE html>
<html>
<body>
<script>
var id = 0;
window.startUpload = function(uploadType, files){ // uploadType区分是控件还是flash
for(var i = 0, file; file = files[i++];){
var uploadObj = new Upload(uploadType, file.fileName, file.fileSize);
uploadObj.init(id++); // 给upload对象设置一个唯一的id
}
};
var Upload = function(uploadType, fileName, fileSize){
console.log('创建一个对象'); // 打印六次
Object.assign(this, { uploadType, fileName, fileSize });
this.dom = null;
};
Upload.prototype.init = function(id){
var that = this;
this.id = id;
this.dom = document.createElement('div');
this.dom.innerHTML = `
<span>文件名称:${this.fileName},文件大小:${this.fileSize}</span>
<button class='delFile'>删除</button>
`;
this.dom.querySelector('.delFile').onclick = function(){
that.delFile();
}
document.body.appendChild(this.dom);
};
Upload.prototype.delFile = function(){
if(this.fileSize < 3000){
return this.dom.parentNode.removeChild(this.dom);
}
if(window.confirm(`确定要删除该文件吗?${this.fileName}`)){
return this.dom.parentNode.removeChild(this.dom);
}
}
startUpload('plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
{
fileName: '2.html',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload('flash', [
{
fileName: '4.txt',
fileSize: 1000
},
{
fileName: '5.html',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 3000
}
])
</script>
</body>
</html>
2.享元模式重构文件上传
<!DOCTYPE html>
<html>
<body>
<script>
var Upload = function(uploadType){
this.uploadType = uploadType;
};
Upload.prototype.delFile = function(id){
uploadManager.setExternalState(id, this); //(1)
if(this.fileSize < 3000){
return this.dom.parentNode.removeChild(this.dom);
}
if(window.confirm(`确定要删除该文件吗?${this.fileName}`)){
return this.dom.parentNode.removeChild(this.dom);
}
}
var UploadFactory = (function(){
var createFlyWeightObjs = {};
return {
create: function(uploadType){
if(createFlyWeightObjs[uploadType]){
return createFlyWeightObjs[uploadType];
}
console.log(`创建upload对象,类型为${uploadType}`); // 只创建了两次
return createFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})();
var uploadManager = (function(){
var uploadDatabase = {};
return {
add: function(id, uploadType, fileName, fileSize){
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML = `
<span>文件名称:${fileName},文件大小:${fileSize}</span>
<button class='delFile'>删除</button>
`;
dom.querySelector('.delFile').onclick = function(){
flyWeightObj.delFile(id);
}
document.body.appendChild(dom);
uploadDatabase[id] = {
fileName,
fileSize,
dom
};
return flyWeightObj;
},
setExternalState: function(id, flyWeightObj){
var uploadData = uploadDatabase[id];
for(var i in uploadData){
flyWeightObj[i] = uploadData[i];
}
}
}
})();
var id = 0;
window.startUpload = function(uploadType, files){
for(var i = 0, file; file = files[i++];){
var uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
}
};
startUpload('plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
{
fileName: '2.html',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload('flash', [
{
fileName: '4.txt',
fileSize: 1000
},
{
fileName: '5.html',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 3000
}
])
</script>
</body>
</html>
对象池是另外一种性能优化方案,它跟享元模式有一些相似之处,但没有分离内部状态和外部状态这个过程。
通用对象池实现
<!DOCTYPE html>
<html>
<body>
<script>
var objectPoolFactory = function(createObjFn){
var objectPool = [];
return {
create: function(){
var obj = objectPool.length === 0 ?
createObjFn.apply(this, arguments) : objectPool.shift();
return obj;
},
recover: function(obj){
objectPool.push(obj);
}
}
};
var iframeFactory = objectPoolFactory(function(){
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = function(){
iframe.onload = null; // 防止iframe重复加载的bug
iframeFactory.recover(iframe); // iframe加载完成后回收节点
}
return iframe;
});
var iframe1 = iframeFactory.create();
iframe1.src = 'https://www.hao123.com/';
var iframe2 = iframeFactory.create();
iframe2.src = 'http://QQ.com';
setTimeout(function(){
var iframe3 = iframeFactory.create();
iframe3.src = 'https://ai.taobao.com/';
}, 3000);
</script>
</body>
</html>
九、职责链模式
把一个充满if-else的函数拆分成多个函数,可以去掉许多嵌套的条件分支语句。
1.商城给出了几种优惠政策,在正式购买后,已经支付过500元定金的用户会收到100元的商城优惠,200元定金的用户可以收到50元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。
var order500 = function(orderType, pay, stock){
if(orderType === 1 && pay === true){
console.log('500元定金预购,得到100元优惠券');
}else{
return 'nextSuccess'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function(orderType, pay, stock){
if(orderType === 2 && pay === true){
console.log('200元定金预购,得到50元优惠券');
}else{
return 'nextSuccess'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function(orderType, pay, stock){
if(stock > 0){
console.log('普通购买,无优惠券');
}else{
return '手机库存不足';
}
};
Function.prototype.after = function(fn){
var self = this;
return function(){
var ret = self.apply(this, arguments);
if(ret === 'nextSuccess'){
return fn.apply(this, arguments);
}
return ret;
}
};
// 可以动态的指定下一节点,也可以很方便地填加节点而不需要改动原代码,符合开发-封闭原则。
var order = order500.after(order200).after(orderNormal);
order(1, true, 500);
order(2, true, 500);
order(1, false, 500);
十、中介者模式
现实中的中介者:机场指挥塔、博彩公司
1、泡泡堂游戏普通实现,这段代码每个玩家和其它玩家都是紧紧耦合在一起的。在此代码中,每个玩家对象都有两个属性,this.partners和this.enemies,用来保存其它玩家对象的引用。当每个对象的状态发生改变,比如角色移动,吃到道具或者死亡时,都必须要显示地遍历通知其它对象。如果在大型游戏中,可能还会有玩家掉线,红队变蓝队这种case,那这段代码就只能迅速进入投降模式了
var players = [];
function Player(name, teamColor){
this.partners = []; // 队友列表
this.enemies = []; //敌人列表
this.state = 'live'; // 玩家列表
this.name = name;
this.teamColor = teamColor;
};
Player.prototype.win = function(){ // 玩家团队胜利
console.log(`winner:${this.name}`);
};
Player.prototype.lose = function(){ // 玩家团队失败
console.log(`loser:${this.name}`);
};
Player.prototype.die = function(){ // 玩家死亡
var all_dead = true;
this.state = 'dead'; // 设置玩家死亡状态为死亡
for(var i = 0, partner; partner = this.partners[i++];){ // 遍历队友列表
if(partner.state !== 'dead'){ // 如果还有一个队友没有死亡,则游戏还未失败
all_dead = false;
break;
}
}
if(all_dead === true){
this.lose(); // 通知队友全部死亡
for(var i = 0, partner; partner = this.partners[i++];){ //通知所有队友玩家游戏失败
partner.lose();
}
for(var i = 0, enemy; enemy = this.enemies[i++];){ // 通知所有敌人游戏胜利
enemy.win();
}
}
}
var playerFactory = function(name, teamColor){
var newPlayer = new Player(name, teamColor); // 创建新玩家
for(var i = 0, player; player = players[i++];){
if(player.teamColor === newPlayer.teamColor){ // 如果是同一队的玩家
player.partners.push(newPlayer); // 相互添加到队友列表
newPlayer.partners.push(player);
}else{
player.enemies.push(newPlayer); // 相互添加到敌人列表
newPlayer.enemies.push(player);
}
}
players.push(newPlayer);
return newPlayer;
};
// 红队
var player1 = playerFactory('皮蛋', 'red'),
player2 = playerFactory('小乖', 'red'),
player3 = playerFactory('宝宝', 'red'),
player4 = playerFactory('小强', 'red');
// 蓝队
var player5 = playerFactory('黑妞', 'blue'),
player6 = playerFactory('葱头', 'blue'),
player7 = playerFactory('胖墩', 'blue'),
player8 = playerFactory('海盗', 'blue');
player1.die();
player2.die();
player3.die();
player4.die();
2.泡泡堂游戏中介者模式实现
可以看到,除了中介者本身,没有一个玩家知道其它玩家的存在,玩家与玩家之间的耦合关系已经完全解除,某个玩家的任何操作都不需要通知其它玩家,而只需要给中介者发送一个消息,中介者处理完消息之后会把结果反馈给其他玩家对象。我们还可以继续给中介者扩展更多功能,以适应游戏需求的不断变化。
function Player(name, teamColor){
this.name = name; // 角色名字
this.teamColor = teamColor; // 队伍颜色
this.state = 'alive'; // 玩家生存状态
};
Player.prototype.win = function(){
console.log(`${this.name} won`);
};
Player.prototype.lose = function(){
console.log(`${this.name} lost`);
};
Player.prototype.die = function(){
this.state = 'dead';
playerDirector.ReceiveMessage('playerDead', this); // 给中介者发送消息,玩家死亡
};
Player.prototype.remove = function(){
playerDirector.ReceiveMessage('removePlayer', this); // 给中介者发送消息,移除一个玩家
};
Player.prototype.changeTeam = function(color){
playerDirector.ReceiveMessage('changeTeam', this, color); // 给中介者发送消息,玩家换队
};
var playerFactory = function(name, teamColor){
var newPlay = new Player(name, teamColor); // 创造一个新的对象
playerDirector.ReceiveMessage('addPlayer', newPlay); // 给中介者发送消息,新增玩家
return newPlay;
};
var playerDirector = (function(){
var players = {}, // 保存所有的玩家
operations = {}; // 中介者可以执行的操作
operations.addPlayer = function(player){
var teamColor = player.teamColor; // 玩家的队伍颜色
players[teamColor] = players[teamColor] || [];
players[teamColor].push(player); // 添加玩家进队伍
};
operations.removePlayer = function(player){
var teamColor = player.teamColor, // 玩家的队伍颜色
teamPlayers = players[teamColor] || []; // 该队伍所有成员
for(var i = teamPlayers.length - 1; i >=0; i--){ // 遍历删除
if(teamPlayers[i] === player){
teamPlayers.splice(i, 1);
}
}
};
operations.changeTeam = function(player, newTeamColor){ // 玩家换队
operations.removePlayer(player); // 从原队伍中删除
player.teamColor = newTeamColor; // 改变队伍颜色
operations.addPlayer(player); // 增加到新队伍中
};
operations.playerDead = function(player){ // 玩家死亡
var teamColor = player.teamColor,
teamPlayers = players[teamColor]; // 玩家所在队伍
var all_dead = true;
for(var i = 0, player; player = teamPlayers[i++];){
if(player.state !== 'dead'){
all_dead = false;
break;
}
}
if(all_dead === true){ // 全部死亡
for(var i = 0, player; player = teamPlayers[i++];){
player.lose(); // 本队所有玩家lose
}
for(var color in players){
if(color !== teamColor){
var teamPlayers = players[color]; // 其它队伍的玩家
for(var i = 0, player; player = teamPlayers[i++];){
player.win();
}
}
}
}
}
var ReceiveMessage = function(){
var message = Array.prototype.shift.call(arguments); // arguments的第一个参数为消息名称
operations[message].apply(this, arguments);
};
return {
ReceiveMessage
}
})();
// 红队
var player1 = playerFactory('皮蛋', 'red'),
player2 = playerFactory('小乖', 'red'),
player3 = playerFactory('宝宝', 'red'),
player4 = playerFactory('小强', 'red');
// 蓝队
var player5 = playerFactory('黑妞', 'blue'),
player6 = playerFactory('葱头', 'blue'),
player7 = playerFactory('胖墩', 'blue'),
player8 = playerFactory('海盗', 'blue');
// player1.die();
// player2.die();
// player3.die();
// player4.die();
// player1.remove();
// player2.remove();
// player3.die();
// player4.die();
player1.changeTeam('blue');
player2.die();
player3.die();
player4.die();
中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识法则,是指一个对象应该尽可能少地去了解另外的对象(类似不和陌生人说话),如果对象耦合性太高,一个对象改变之后,难免会影响到其他对象。
中介者缺陷:最大的缺陷是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者经常是巨大的。中介者对象自身往往就是一个难以维护的对象,有必要时再考虑使用中介者模式。
十一、装饰者模式
数据上报、统计函数执行时间、动态改变函数参数以及插件式的表单验证都可以应用装饰者模式。
1.插件式的表单验证
Function.prototype.before = function(beforefn){
var _self = this;
return function(){
if(beforefn.apply(this, arguments) === false){
// beforefn返回false的情况直接return,不再执行后面的原函数
return;
}
return _self.apply(this, arguments);
}
}
var validate = function(){
if(username.value === ''){
alert('用户名不能为空');
return false;
}
if(password.value === ''){
alert('密码不能为空');
return false;
}
}
var formSubmit = function(){
var param = {
username: username.value,
password: password.value
}
ajax('http://xxx.com/login', param);
}
formSubmit = formSubmit.before(validate);
submitBtn.onclick = function(){
formSubmit();
}
这段代码中,校验和提交表单完全分离开来,他们不再有任何耦合关系。但是,装饰者模式也叠加了函数的作用域,如果装饰的链条过长,性能上也会受到一些影响。
十二、状态模式
文件上传,音乐、视频播放,游戏格斗(比如跌倒时不能攻击),TCP请求有建立连接、监听、关闭等状态。状态模式的优点是把事物封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部。
以电灯的开关为例子,电灯的状态不同那点击开关的行为是不一样的,可以用状态模式消除大量的if-else分支。
<!DOCTYPE html>
<html>
<body>
<script>
// OffLightState;
var OffLightState = function(light){
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
console.log('弱光'); // offLightState对应的行为
this.light.setState(this.light.weakLightState); //切换到weakLightState
};
// WeakLightState
var WeakLightState = function(light){
this.light = light;
}
WeakLightState.prototype.buttonWasPressed = function(){
console.log('强光');
this.light.setState(this.light.strongLightState);
}
// StrongLightState
var StrongLightState = function(light){
this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function(){
console.log('关灯');
this.light.setState(this.light.offLightState);
};
var Light = function(){
this.offLightState = new OffLightState(this);
this.weakLightState = new WeakLightState(this);
this.strongLightState = new StrongLightState(this);
this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement('button'),
self = this;
this.button = document.body.appendChild(button);
this.button.innerHTML = '开关';
this.currState = this.offLightState; // 设置当前状态
this.button.onclick = function(){
self.currState.buttonWasPressed();
}
};
Light.prototype.setState = function(newState){
this.currState = newState;
};
var light = new Light();
light.init();
</script>
</body>
</html>
状态模式的缺点是会在系统中定义很多个类,编写20个状态类是一项枯燥乏味的工作,而且系统中会因此而增加不少对象。另外,由于逻辑分布在状态类中,虽然避开了不受欢迎的条件分支,但也造成逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑。
十三、适配器模式
适配器模式是一种亡羊补牢的模式,没有人会在程序的设计之初就使用它
渲染地图:谷歌地图提供的不是renderMap需要的show方法,而是display方法,在不该动renderMap的情况下可以借助适配器模式来实现
var renderMap = function(map){
if(map.show instanceof Function){
map.show(); }
};
var googleMap = {
show: function(){
console.log('开始渲染谷歌地图');
}
};
var baiduMap = {
display: function(){
console.log('开始渲染百度地图');
}
};
var baiduMapAdapter = {
show: function(){
return baiduMap.display();
}
};
renderMap(googleMap);
renderMap(baiduMapAdapter);
十四、设计原则与编程技巧
单一职责原则:例如代理模式、迭代器模式、单例模式、装饰者模式。
最少知识原则:中介者模式。
开放-封闭原则:几乎所有的好的设计模式都符合这一原则。其它设计原则都是达到这个目标的过程。让程序保持完全封闭是做不到的,就算技术上做得到,也需要花费太多的时间和精力。而且让程序符合开发-封闭原则的代价是引入更多的抽象层次,更多的抽象有可能会增加代码的复杂度。