说设计模式之前首先要先了解一下有什么设计原则,也就是你新写一个模式,要遵循什么样的原则。
一、设计原则
1、单一职责原则:也就是
一个对象或方法只做一件事情。尽可能的将对象划分成较小的粒度。
2、最少知识原则:
对象之间尽可能少进行通信,不要发生直接的交互,如果对象之间有需要进行通讯的,可以转交给第三方进行处理。
3、开放-封闭原则:实体(类、模块、函数)等应该是可以拓展等,但是
不可以修改,当需要改变一个程序等功能或者给这个程序增加新的功能等时候,可以使用增加代码的方式,尽量避免改变程序的源代码,保持系统的稳定性。
总的来说就是,一个对象就让它负责一件事情,并且不要修改它原有的代码
二、设计模式
我们总说设计模式设计模式,那么设计模式究竟是什么?我个人的理解就是解决方案的模版,遇到同样的业务场景可以使用同样的设计模式,这样我们写代码的时候可复用性就高了。
1、单例模式
故名思义,单例模式就是保证全局就只有一个实例,并且全局可以访问。
我以前做的一个项目里面,设计到蓝牙连接,这个蓝牙类在全局就只可以拥有一个实例,那么怎么实现单例呢?
首先,我们创建一个全局的对象,代表这个实例,然后判断实例是否已经创建了,如果是直接返回,如果没有调用这个类的构造函数,并赋值给这个实例,然后返回。
//1、使用代理实现单例模式,就是将管理单例的代码独立出来
function SetSingleton(callback){
var instance = null
return function(){
if(!instance){
instance = callback.apply(this,arguments)
}
return instance
}
}
//单例1:coder
function Setcode(code){
this.code = code
}
Setcode.prototype.getName = function(){
console.log('coder',this.code)
}
var Coder = SetSingleton(function(code){
//实例一个类
var code = new Setcode(code)
return code
})
Coder('1').getName()
Coder('2').getName()
Coder('3').getName()
//单例2 pmer
function SetPm(pm){
this.pm = pm
}
SetPm.prototype.getName = function(pm){
console.log('pmer',this.pm)
}
var Pmer = SetSingleton(function(pm){
var pm = new SetPm(pm)
return pm
})
Pmer('pm1').getName()
Pmer('pm2').getName()
Pmer('pm3').getName()
在JavaScript中,除了使用代理实现单例,我们还可以实现惰性单例。什么是惰性单例呢?就是这个实例,直到要使用它的时候,我才给他创建。
var getSingle = function( fn ){
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
};
上面这个fn对应的是,需要管理单例的对象初始化的回调函数。比如我现在需要创建一个全局唯一的div,那么对应的就是
var createDiv = function(){
var div = document.createElement( 'div' );
div.innerHTML = '我是新创建的div';
div.style.display = 'none';
document.body.appendChild( div );
return div;
};
var createSingleDiv = getSingle( createDiv);
document.getElementById( 'cteareDiv' ).onclick = function(){
var div = createSingleDiv();
div.style.display = 'block';
};
2、策略模式
什么是策略模式呢?策略模式指的是定义一系列的算法,把它们一个个封装起来。就是将算法的使用和算法的实现分离开来。一个基于策略模式的程序至少由两部分组成。第一个部分是
一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类context,context
接受客户的请求,随后把请求委托给某一个策略类。
下面我们来慢慢分析,一个策略模式在JavaScript中如何实现
1、首先我们封装一个策略类,在js中我们无需定义一个类,直接使用一个对象来封装这个策略类
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
}
};
这个策略类中一共有三个具体的算法分别是S,A,B每个算法都有不一样的实现
2、接下来实现一个环境类context,用来接收客户的请求,并把请求根据需求委托给策略类
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
在这个环境类中接收两个参数,一个是等级,一个是工资,这个等级对应了策略类中不同的实现。
3、接下来就可以使用了
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000
console.log( calculateBonus( 'B', 10000 ) ); // 输出:20000
下面我们用策略类来写一个页面检测表单的
<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>
</body>
<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;
}
}
};
/***********************Validator 类**************************/
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( ':' );
console.log('strategyAry',strategyAry)
var errorMsg = rule.errorMsg;
self.cache.push(function(){
// strategyAry是传给策略类的参数数组,第一个是输入的值,最后一个是错误信息,中间的保留传入的参数比如长度
var strategy = strategyAry.shift();
strategyAry.unshift( dom.value );
strategyAry.push( errorMsg );
console.log('strategyAry2',strategyAry)
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:6',
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(){
var errorMsg = validataFunc();
if ( errorMsg ){
alert ( errorMsg );
return false;
}
};
</script>
</html>
3、代理模式
我们在第一个单例模式的时候其实就有用到代理了,那么代理模式又是什么呢?这个代理其实就像中介,像我们在安居客上面 租房,我们首先接触到的不是真正的房东,而是那些地产中介,我们和地产中介进行联系以后,中介就会帮助我们去联系一个真正的房东。所以这里的中介就是一个代理

代理又分为保护代理和虚拟代理,其中保护代理是,在代理中可以拒绝掉一些对实体的请求,这样可以保证对实体的请求一定是可靠有用的。虚拟代理就是,将一些开销很大的对象,延迟到真正需要它的时候才去创建。必须图片预加载。
那么实现代理模式有什么需要遵循的原则呢?
首先,也是最重要的,代理和本体的接口要一致,这样客户不用知道你是代理还是本体,我请求的接口是一样的。
接下来我们用虚拟代理来合并请求。
在一些场合中,我们列表中的每个元素只要被点中就给服务器发送一个请求,如果我们可以再一秒中点击多个元素,那么就会有多个请求发送给服务器了,如果大量的这样操作,将会给服务器造成很大的压力。所以我们可以使用代理,来在一段时间内把若干个请求一起发送给服务器
<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
<input type="checkbox" id="6"></input>6
<input type="checkbox" id="7"></input>7
<input type="checkbox" id="8"></input>8
<input type="checkbox" id="9"></input>9
</body>
<script>
//异步请求
var syncRequest = function(id){
console.log('我要给服务器发送请求啦',id)
}
//代理请求
proxySyncRequest = (function(id){
var cache = [],timer
return function(id){
cache.push(id)
if(timer){
return
}
timer = setTimeout(function(){
let id = cache.join(',')
syncRequest(id)//发送多个id的请求
clearTimeout(timer)//清除定时器
timer = 0
cache = []
},2000)
}
})()
var checkbox = document.getElementsByTagName('input')
for(let i = 0,c;c = checkbox[i++];){
c.onclick = function(){
if(this.checked == true){
proxySyncRequest(this.id)
}
}
}
</script>
</html>
接下来,我们来实现一个缓存代理。
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
console.log('计算乘积',a)
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 )); // 输出:24
console.log(proxyMult( 1, 2, 3, 4 )); // 输出:24
可以看到我们了两次参数一样,但是实际上只调用了一次。
接下来我们可以修改代码而使得这个代理可以代理多个操作
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
console.log('计算乘积',a)
return a;
};
var createProxyFactory= function(fn){
console.log(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 );
}
}
proxyMult = createProxyFactory(mult)
console.log(proxyMult( 1, 2, 3, 4 )); // 输出:24
console.log(proxyMult( 1, 2, 3, 4 )); // 输出:24
4、发布-订阅模式(观察者模式)
实现一个发布-订阅模式,分别有三个步骤
-
首先要指定好谁要充当发布者
-
给发布者添加一个缓存列表,用来存放回调函数(订阅者订阅时传入的回调函数)
-
当事件发生的时候,发布者会遍历缓存列表,一次触发里面存放的订阅者回调函数
下面我们来写一个简单的发布-订阅
let event = {
clientList:[],
listen:function(key,fn){
if(!this.clientList[key]){
this.clientList[key] = []
}
this.clientList[key] = fn
},
trriger:function(){
let key = Array.prototype.shift.call(arguments)//获取到特定的key
let fns = this.clientList[key]
if(!fns || fns.length == 0){
//没有指定的回调不作处理
return
}
for(let i = 0; i<fns.length;i++){
let fn = fns[i]
fn.apply(this,arguments)
}
}
}
那么要怎么调用呢?首先我们先给需要的对象绑定,也就是实例化
//给一个新的对象,绑定所有event的属性(深拷贝)
let installEvent = function(obj){
for(let i in event){
obj[i] = event[i]
}
}
let sales = {}
installEvent(sales)
sales.listen('mike',function(price){//第一个顾客订阅牛奶的价格
console.log('牛奶的价格是:',price)
})
sales.listen('apple',function(price){//第二个顾客订阅苹果的价格
console.log('苹果的价格是',price)
})
//超市发布牛奶的价格
sales.trriger('mike',5)
//超市发布苹果的价格
sales.trriger('apple',10)
接下来我们又想到了一个需求,假如顾客只想知道一次商品的价格,此外超市再发布同样商品的价格的时候我不想知道了,那要怎么办呢?我们是不是可以想到取消订阅?为了增加取消订阅的功能,我们在原有的基础上新加一个remove功能,用来移除对应的事件
let event = {
clientList:[],
listen:function(key,fn){
if(!this.clientList[key]){
this.clientList[key] = []
}
this.clientList[key].push(fn)
},
trriger:function(){
let key = Array.prototype.shift.call(arguments)//获取到特定的key
let fns = this.clientList[key]
if(!fns || fns.length == 0){
//没有指定的回调不作处理
return
}
for(let i = 0; i<fns.length;i++){
let fn = fns[i]
fn.apply(this,arguments)
}
},
remove:function(key,fn){
let fns = this.clientList[key]
if(!fns || fns.length == 0){
//没有人订阅
return
}
if(!fn){
//有人订阅但是没传入具体的函数,就是该事件所有的回调都要删除
fns = []
fns.length = 0
}else{
//只取消订阅事件的某个函数,也就是只有一个顾客取消订阅该事件
for(let i = 0;i<fns.length;i++){
let _fn = fns[i]
console.log('jjj',_fn,fn)
if(_fn === fn){
fns.splice(i,1)
console.log('移除之后的fns',fns)
}
}
}
}
}
//给一个新的对象,绑定所有event的属性
let installEvent = function(obj){
for(let i in event){
obj[i] = event[i]
}
}
let sales = {}
installEvent(sales)
let milefn1 = function(price){//第一个顾客订阅牛奶的价格
console.log('第一个顾客订阅牛奶的价格是:',price)
}
sales.listen('mike',milefn1)
sales.listen('apple',function(price){//第二个顾客订阅苹果的价格
console.log('第二个顾客订阅苹果的价格是',price)
})
sales.listen('mike',function(price){//第三个顾客订阅牛奶的价格
console.log('第三个顾客订阅牛奶的价格是:',price)
})
//超市发布牛奶的价格
sales.trriger('mike',5)
//超市发布苹果的价格
sales.trriger('apple',10)
//顾客a取消订阅发布牛奶的时间
sales.remove('mike',milefn1)
sales.trriger('mike',5)
到了这里,想必你也会有一个疑虑,上面实现的发布-订阅模式还是有点问题。比如,我在a超市想要订阅牛奶的价格,但是有想要订阅b超市牛奶的价格,这个时候又得重新实现一次b超市的发布订阅模式。有没有可能做一个全局的,超市想要发布的时候直接去这个全局变量发布,订阅的时候也在这个全局变量中进行订阅。
其实这个非常简单,我们把Event当做一个全局变量就可以了,不去实例化
let Event = {
clientList:[],
listen:function(key,fn){
if(!this.clientList[key]){
this.clientList[key] = []
}
this.clientList[key].push(fn)
},
trriger:function(){
let key = Array.prototype.shift.call(arguments)//获取到特定的key
let fns = this.clientList[key]
if(!fns || fns.length == 0){
//没有指定的回调不作处理
return
}
for(let i = 0; i<fns.length;i++){
let fn = fns[i]
fn.apply(this,arguments)
}
},
remove:function(key,fn){
let fns = this.clientList[key]
if(!fns || fns.length == 0){
//没有人订阅
return
}
if(!fn){
//有人订阅但是没传入具体的函数,就是该事件所有的回调都要删除
fns = []
fns.length = 0
}else{
//只取消订阅事件的某个函数,也就是只有一个顾客取消订阅该事件
for(let i = 0;i<fns.length;i++){
let _fn = fns[i]
console.log('jjj',_fn,fn)
if(_fn === fn){
fns.splice(i,1)
console.log('移除之后的fns',fns)
}
}
}
}
}
Event.listen('mike',function(price){//第一个顾客订阅牛奶的价格
console.log('订阅牛奶的价格',price)
})
Event.trriger('mike',5)//第一个超市发布牛奶的价格
Event.trriger('mike',21)//第二个超市发布牛奶的价格
5、命令模式
什么是命令模式呢?命令是什么?主要应用在什么场景呢?
命令模式中的命令指的是一个执行某些特定时期的指令,最常见的应用场景是:有时候要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接受者能够消除彼此之间的耦合关系。
//给按钮安装命令,就是给具体的对象去添加具体的命令
let setCommand = function(button,command){
button.onclick = function(){
command.execute()
}
}
//按钮的定义,对象具体的功能
let MenuBar = {
refresh:function(){
console.log('刷新页面')
}
}
//封装命令类
let RefreshMenuBarCommand = function(receiver){
return {
execute:function(){
receiver.refresh()
}
}
}
//使用
let refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
setCommand(document.getElementById('button1'),refreshMenuBarCommand)
现在已经实现了基本的命令模式,如果我们添加一个撤销功能呢?就像我们下棋一样,一个棋子就是一条命令,把它放在棋盘上的一个位子就是执行它,悔棋就是撤销这条命令。下面一起来看一下怎么实现撤销。
每下一步棋之前,都记录下来这个棋子在棋盘上的位子,撤销的时候就把棋子移动到这个已经记录下来的原来位置。
//定义一个棋子
let MoveCommand = function(reciever,pos){
this.reciever = reciever
this.pos = pos
this.oldPos = null
}
//定义下棋的操作
MoveCommand.prototype.execute = function(){
this.reciever.start(this.pos)//移动到当前位置
this.oldPos = this.receiver.dom.getBoundingClientRect()[ this.receiver.propertyName ]//记录移动前的位置
}
//定义撤销的操作
MoveCommand.prototype.unexecute = function(){
this.reciever.start(this.oldPos)
}
6、组合模式
所谓组合模式,就是很多小的命令组成一个宏命令,宏命令之间又可以组成一个宏命令,如此往复,组成一颗茁壮的树。等到执行这个组合命令的时候,就遍历这棵树,直至所有的叶节点都被执行完成。
/******************************* 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 );
};
/**接下来创建一些文件夹和文件对象, 并且让它们组合成一棵树,这棵树就是我们 F 盘里的现有文件目录结构:*/
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();
需要注意的是,组合模式不是父子关系,组合对象把请求委托给它所包含的所有叶对象,所有的对象都拥有一个统一的接口。此外,对所有的叶对象的操作要有一致性,就是所有叶对象都要执行。还有,一个叶对象只能从属于一个父节点,不能被执行两次。
组合模式适用的情况:
-
表示对象的部分-整体 层次结构。组合模式可以方便地构造一棵树来表示对象的 部分-整体 结构。
-
希望统一对待树中所有的对象。组合模式可以忽略组合对象和叶对象的区别,在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就不用写一堆的if else来分别处理。
7、模板方法模式
(1)定义:模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常
在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。在模板方法模式中,子类实现中相同的部分被上移到父类中,而将不同部分的留给子类来实现。
(2)那什么才算抽象类呢?
类有两种,一种是具体类,一种是抽象类。具体类可以被实例化,抽象类不能被实例化。比如我们去便利店买一瓶饮料,我们和店员说,我要买一瓶饮料,这是不行的,因为要什么饮料呢?饮料只是一个抽象名词,只有当我们真正明确了饮料类型后,才能得到一瓶可乐、咖啡或茶。
那既然不能实例化,抽象类的作用是什么呢?抽象类就是表示一个一致对外的接口。继承了这个抽象类的所有子类,都将拥有跟抽象类一致的接口方法,
抽象类的主要作用就是为它的子类定义这些公共接口。
class Beverage{
// 抽象饮料类
init(){
this.boilWater()//把水煮沸
this.brew()//冲泡饮料
this.pourInCup()//把饮料倒进被子里
this.addCondiments()//加入调料
}
boilWater(){
console.log('煮沸水')
}
brew(){
throw new Error('子类必须实现brew方法')
}
pourInCup(){
throw new Error('子类必须实现pourInCup方法')
}
addCondiments(){
throw new Error('子类必须实现addCondiments方法')
}
}
class Coffee extends Beverage{
brew(){
console.log('用沸水冲泡咖啡')
}
pourInCup(){
console.log('把咖啡倒进被子里')
}
addCondiments(){
console.log('加糖和牛奶')
}
}
class Tea extends Beverage{
brew(){
console.log('把沸水浸泡茶叶')
}
pourInCup(){
console.log('把茶倒进杯子里')
}
addCondiments(){
console.log('加柠檬')
}
}
class Test{
prepareRecipe(beverage){
beverage.init()
}
main(){
let coffee = new Coffee()
this.prepareRecipe(coffee)
let tea = new Tea()
this.prepareRecipe(tea)
}
}
let test1 = new Test()
test1.main()
(3)模板方法模式的使用场景
模板方法模式经常被架构师用于搭建项目的框架,架构师定好了框架的股价,程序员继承框架的结构以后,负责往里填空。
(4)使用高阶函数实现模板方法
var Beverage = function(params){
var boilWater = function(){
console.log('把水煮沸')
}
var brew = params.brew || function(){
throw new Error('子类必须实现brew')
}
var pourInCup = params.pourInCup || function(){
throw new Error('子类必须实现pourInCup')
}
var addCondiments = params.addCondiments || function(){
throw new Error('子类必须实现addCondiments')
}
var F = function(){}
F.prototype.init = function(){
boilWater()
brew()
pourInCup()
addCondiments()
}
return F
}
var Coffee = Beverage({
brew(){
console.log('用沸水冲泡咖啡')
},
pourInCup(){
console.log('把咖啡倒进杯子里')
},
addCondiments(){
console.log('加糖和牛奶')
}
})
var coffee = new Coffee()
coffee.init()
8、享元模式
(1)享元是什么?
享元模式是一种用于性能优化的模式,核心是运用共享技术来有效支持大量细粒度的对象。
享元模式要求将对象的属性划分为内部状态和外部状态(状态指的是属性),享元模式的目标是尽量减少共享对象的数量。至于如何划分内部状态和外部状态,下面的几条经验提供了一些指引:
-
内部状态存储于对象内部
-
内部状态可以被一些对象共享
-
内部状态独立于具体的场景,通常不会改变
-
外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
这样以来,我们就可以
把所有内部状态相同的对象都指定为同一个共享的对象,而外部状态可以从对象身上剥离出来,并储存在外部。外部状态在必要时,传入共享对象组成一个完整的对象。
(2)享元模式的实例
var Upload = function(uploadType){
this.uploadType = uploadType//内部状态
}
Upload.prototype.delFile = function(id){
uploadManager.setExternalState(id,this)
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 createdFlyWeightObjs = {}
return {
create:function(uploadType){
if(createdFlyWeightObjs[uploadType]){//如果已经创建过这个对象则在缓冲池中直接返回这个对象
return createdFlyWeightObjs[uploadType]
}
return createdFlyWeightObjs[uploadType] = new Upload(uploadType)
}
}
})()
var uploadManager = (function(){
var uploadDatabase = {}//保存所有upload对象的外部状态,以便在程序运行过程中给upload对象共享对象设置外部状态
return {
add:function(id,uploadType,fileName,fileSize){
var flyWeightObj = UploadFactory.create(uploadType)//同一个类型的上传文件,使用的是同一个flyWeightObj
var dom = document.createElement('div')
dom.innerHTML = '<span>文件名称:'+ fileName +', 文件大小: '+ fileSize +'</span>' + '<button class="delFile">删除</button>'
dom.querySelector('.delFile').onclick = function(){
console.log(id)
flyWeightObj.delFile(id)
}
document.body.appendChild(dom)
uploadDatabase[id] = {
fileName:fileName,
fileSize:fileSize,
dom:dom
}
return flyWeightObj
},
setExternalState:function(id,flyWeightObj){
//设置外部对象,因为所有上传的文件时共享着同一个对象,所以在处理每一个对象的时候,需要将外部状态设置为改文件的外部状态。
var uploadData = uploadDatabase[id]
for(var i in uploadData){
flyWeightObj[i] = uploadData[i]
}
console.log(uploadData,flyWeightObj)
}
}
})()
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 );
}
//遍历完所有上传文件后,实际上只创建了一个uploadObj,也就是说,所有同类型的上传文件共享了同一个上传对象
console.log(uploadObj)
}
(3)享元模式的适用性
享元模式时一种很好的性能优化的方案,它带来的好处很大程度上取决于如何使用以及何时使用。
-
一个程序中使用了大量相似的对象
-
由于使用了大量的对象,造成很大的内存开销
-
对象的大多数状态都可以变为外部状态
-
抽离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
9、职责链模式
(1)职责链是什么?
职责链模式的定义就是,
使所有对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

请求发送者只需要直到链中的第一个节点就可以把请求发出去,从而弱化了发送者和接收者之间的强联系。
(2)实例
var order500 = function(orderType,pay,stock){
if(orderType == 1 && pay == true){
console.log('500元定金预购,得到100元优惠券')
}else{
return 'nextSuccessor'
}
}
var order200 = function(orderType,pay,stock){
if(orderType == 2 && pay == true){
console.log('200元定金预购,得到100元优惠券')
}else{
return 'nextSuccessor'
}
}
var orderNormal = function(orderType,pay,stock){
if(stock > 0){
console.log('普通购买,无优惠券')
}else{
console.log('手机库存不足')
}
}
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)//把请求往后面传,记得传入的对象要正确
}
}
var chainOrder500 = new Chain(order500)
var chainOrder200 = new Chain(order200)
var chainOrderNormal = new Chain(orderNormal)
//指定职责链中的顺序
chainOrder500.setNextSuccessor(chainOrder200)
chainOrder200.setNextSuccessor(chainOrderNormal)
//开始请求
chainOrder500.passRequest(1,true,500)
chainOrder500.passRequest(2,true,500)
chainOrder500.passRequest(3,true,500)
chainOrder500.passRequest(1,false,0)
(3)将上面的实例进行修改,使得异步请求也可以使用
var Chain = function(fn){
this.fn = fn
this.successor = null
}
Chain.prototype.setNextSuccessor = function(successor){
return this.successor = successor
}
Chain.prototype.next = function(){
return this.successor && this.successor.passRequest.apply(this.successor,arguments)//把请求往后面传,记得传入的对象要正确
}
Chain.prototype.passRequest = function(){
var ret = this.fn.apply(this,arguments)//先调用一下自己,再看下返回结果
if(ret == 'nextSuccessor'){
return this.successor && this.successor.passRequest.apply(this.successor,arguments)//把请求往后面传,记得传入的对象要正确
}
}
var fn1 = new Chain(function(){
console.log(1)
return 'nextSuccessor'
})
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)
fn1.passRequest()
(4)当第一个函数返回‘nextSuccessor’时,将请求继续传递给下一个函数。定义一个after函数,传入一个方法,执行完当前方法以后,当当前方法返回‘nextSuccessor’时候,就执行传入的函数。
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;
}
};
var order = order500yuan.after( order200yuan ).after( orderNormal );
10、中介者模式
(1)中介者的作用是什么?
中介者模式的作用就是解除对象与对象之间的紧耦合关系。所有的相关对象都是通过中介者对象来甜心,而不是相互引用,所以当一个对象发生改变的时候,只需要通知中介者对象就可以了。中介者使各个对象之间耦合松散,而且可以独立地改变它们之间的交互。核心就是将对象和操作的处理分开,交由中介者去进行处理。

(2)用中介者模式来写一个泡泡堂游戏
首先定义Player构造函数和player对象的原型方法,在player对象的这些原型方法中,不再负责具体的执行逻辑,而是
把操作转交给中介者对象。
function Player(name,teamColor){
this.name = name
this.teamColor = teamColor
this.state = 'alive'//玩家生存状态
}
Player.prototype.win = function(){
console.log(this.name + 'win!')
}
Player.prototype.lose = function(){
console.log(this.name + 'lost!')
}
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 = {},operations = {}//players是玩家队伍,operation是中介者可以执行的操作
//新增一个玩家
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
var 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){
//队员所处的队伍全部死亡
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 reciveMessage = function(){
var message = Array.prototype.shift.call(arguments)//获得第一个参数,也就是消息名称
// console.log(message,operations,arguments)
operations[message].apply(this,arguments)
}
return {
reciveMessage:reciveMessage
}
})()
// 红队:
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();
11、装饰者模式
(1)什么是装饰者模式呢?
给对象动态地增加职责的方式成为装饰者模式。
装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
var plane = {
fire:function(){
console.log('发送普通子弹')
}
}
var missileDecorator = function(){
console.log('发送导弹')
}
var atomDecorator = function(){
console.log('发送原子弹')
}
var fire1 = plane.fire
plane.fire = function(){
fire1()
missileDecorator()
}
var fire2 = plane.fire
plane.fire = function(){
fire2()
atomDecorator()
}
plane.fire()
(2) 装饰函数
现在需要一个方法,在不改变函数源码的情况下,给函数增加功能。我们这里实现两个方法,分别是before和after
Function.prototype.after = function(afterFn){
var _self = this
return function(){//返回一个代理函数
var ret = _self.apply(this,arguments)//执行当前函数
afterFn.apply(this,arguments)
return ret
}
}
Function.prototype.before = function(beforeFn){
var _self = this
return function(){
beforeFn.apply(this,arguments)
return _self.apply(this,arguments)//执行原来的函数
}
}
上面的before函数单纯的只是负责函数的执行顺序,没有根据函数的返回结果来进行后续的处理。接下来我们优化一下这个方法,使得beforeFn的执行结果返回false的时候,后面的原函数不再执行
Function.prototype.after = function(afterFn){
var _self = this
return function(){//返回一个代理函数
var ret = _self.apply(this,arguments)//执行当前函数
if(ret === false){ return }
afterFn.apply(this,arguments)
return ret
}
}
Function.prototype.before = function(beforeFn){
var _self = this
return function(){
var ret = beforeFn.apply(this,arguments)
if(ret === false){return}
return _self.apply(this,arguments)//执行原来的函数
}
}
(3)装饰这模式和代理模式的区别
装饰者模式和代理模式看起来非常相像,都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另一个对象的引用,并且都向那个对象发送请求。两者的主要区别就是它们的意图和设计目的,代理模式的目的是,当直接访问本体不方便或者不符合需求的时候,为这个本体提供一个替代者·。装饰者是动态的加入一些行为2.
12、状态模式
(1)什么是状态模式
将状态封装成独立的类,并将请求委托给当前的状态对象每当对象的内部状态改变时,会带来不同的行为变化。我们使用的对象,在不同的状态下具有截然不同的行为,这个对象看起来是从不同的类中实例化来的。
(2)下面我们来看一个简单的例子。
当电灯开关按下的时候,灯会有三个状态:强光、弱光和关闭。所以每次按下开关的时候,就会在这三个状态之间发生变化。
我们首先封装三个状态:WeakLightState弱光、StrongLightState强光、OffLightState关闭,这三个类内部分别是处于这个状态的时候按钮被下的处理方法。

var OffLightState = function(light){
this.light = light
}
OffLightState.prototype.buttonWasPressed = function(){
console.log('当前是关闭状态,切换到弱光')
this.light.setState(this.light.weakLightState)//切换到弱光状态
}
var WeakLightState = function(light){
this.light = light
}
WeakLightState.prototype.buttonWasPressed = function(){
console.log('当前是弱光状态,切换到强光')
this.light.setState(this.light.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')
var _self = this
this.button = document.body.appendChild(button)
this.button.innerHTML = '开关'
this.currentState = this.offLightState //设置默认状态
this.button.onclick = function(){
_self.currentState.buttonWasPressed()
}
}
Light.prototype.setState = function(newState){
this.currentState = newState
}
var light = new Light()
light.init()
(3)改写上面的例子,使其更符合JavaScript的特性
var FSM = {
off:{
buttonWasPressed:function(){
console.log('关灯')
this.button.innerHTML = '下一次按我是开灯'
this.currentState = FSM.on
}
},
on:{
buttonWasPressed:function(){
console.log('开灯')
this.button.innerHTML = '下一次按我是关灯'
this.currentState = FSM.off
}
}
}
var Light = function(){
this.currentState = FSM.off
this.button = null
}
Light.prototype.init = function (){
var button = document.createElement('button')
var _self = this
this.button = document.body.appendChild(button)
this.button.innerHTML = '已关'
this.button.onclick = function(){
_self.currentState.buttonWasPressed.call(_self)//把请求委托给FSM状态机
}
}
var light = new Light()
light.init()