Appearance
设计模式:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案
11.1 单列模式
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
js
var namespace1 = {
a: function(){
alert (1);
},
b: function(){
alert (2);
}
};11.2 策略模式
策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
js
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
r eturn salary * 2;
}
};
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000- 通过高阶函数实现:
js
var S = function( salary ){
return salary * 4;
};
var A = function( salary ){
return salary * 3;
};
var B = function( salary ){
r eturn salary * 2;
};
var calculateBonus = function( func, salary ){
return func( salary );
};
console.log(calculateBonus( S, 20000 ) ); // 输出:80000
console.log(calculateBonus( A, 10000 ) ); // 输出:3000011.3 代理模式
代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问
- 虚拟代理:通过虚拟代理实现图片预加载
js
// 虚拟代理实现图片预加载 图片没加载之前通过本地的图片站位
var myImage = (function(){
// 1. myImage函数自执行:创建dom元素
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
// 5./7.将传来的src放入dom
imgNode.src = src;
}
}
})();
var proxyImage = (function(){
// 2.proxyImage函数自执行:创建图片对象,绑定方法
var img = new Image;
img.onload = function(){
// 6.监听到图片加载完成,调用setSrc方法,将设置的图片传给myImage方法
myImage.setSrc( this.src );
}
return {
setSrc: function( src ){
// 3.用户调用setSrc方法,将默认图片传给myImage方法
myImage.setSrc( 'file://Users/svenzeng/Desktop/loading.gif' );
// 4. img形成闭包,给img设置src
img.src = src;
}
}
})();
proxyImage.setSrc( 'http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );- 缓存代理:通过缓存代理合并http请求,将一段时间内的连续请求合并
js
var synchronousFile = function( id ){
console.log( '开始上传文件,id 为: ' + id );
};
var proxySynchronousFile = (function(){
// 1.初始化
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function( id ){
// 3. 调用proxySynchronousFile的方法 将id插入待处理的集合
cache.push( id );
if ( timer ){ // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒后向上传方法提交ID的合集
clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000 );
}
})();
// 2.连续快速调用proxySynchronousFile方法
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};- 保护代理:用于对象应该有不同访问权限的情况
- 防火墙代理理:控制网络资源的访问
- 远程代理
- 智能引用代理
- 写时复制代理
11.4 迭代器模式
迭代器模式:是指定义一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示 (迭代器模式无非就是循环访问聚合对象中的各个元素)
- 内部迭代器
js
var each = function( ary, callback ){
for ( var i = 0, l = ary.length; i < l; i++ ){
callback.call( ary[i], i, ary[ i ] ); // 把下标和元素当作参数传给 callback 函数
}
};
each( [ 1, 2, 3 ], function( i, n ){
alert ( [ i, n ] );
});- 外部迭代器
js
var Iterator = function( obj ){
var current = 0;
var next = function(){
current += 1;
};
var isDone = function(){
return current >= obj.length;
};
var getCurrItem = function(){
return obj[ current ];
};
return {
next: next,
isDone: isDone,
getCurrItem: getCurrItem
}
};
var iterator1 = Iterator( [ 1, 2, 3 ] );
iterator1.next() // 迭代下一个元素
iterator1.getCurrItem() // 当前元素
iterator1.isDone() // 迭代是否结束11.5 发布-订阅模式(观察者模式)
发布-订阅模式:定义一个对象,当这个对象的状态发生改变时,所有依赖于它的对象都将得到通知
发布-订阅模式是最常用的模式之一,无论是MVC还是MVVM,都少不了发布—订阅模式的参与,而且JavaScript本身也是一门基于事件驱动的语言
- 定义一个发布-订阅模式模型
js
var event = {
clientList: [], // 所有订阅的客户
// 订阅
listen: function( name,key, fn ){ // 登记订阅的客户姓名、通讯方式fn(回调函数)和所订阅信息key
if ( !this.clientList[ key ] ){
this.clientList[ key ] = [];
}
this.clientList[ key ].push( {name,fn} ); // 根据订阅的消息将客户分类
},
// 取消订阅
remove: function( name,key, fn ){ // 取消用户订阅
var list = this.clientList[ key ];
if ( !fns ){ // 如果消息key没有被人订阅过 直接跳过
return false;
}
if ( !name ){ // 如果没有告诉具体的人,则删除消息key下的所有订阅人
list && ( list.length = 0 );
}else{
for ( var l = list.length - 1; l >=0; l-- ){ // 反向遍历订阅的回调函数列表
if ( list[ l ].name === name ){
list.splice( l, 1 ); // 删除订阅记录
fn.apply( this, ['取消成功'] ); // 通过fn给客户发送取消订阅成功的消息
}
}
}
},
// 发布消息
trigger: function(){ // 为订阅的客户发送消息
var [key,msg] = arguments
list = this.clientList[ key ]; // 取出不同消息的订阅客户
if ( !list || list.length === 0 ){ // 如果该消息没有客户订阅 则不发布
return false;
}
for( var i = 0, item; item = list[ i++ ]; ){
item.fn.apply( this, arguments ); // 通过fn给客户发送消息
}
}
};
// 为发布-订阅模式模型的使用者 安装注册相关方法
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};- 使用发布-订阅模式模型 订阅、取消订阅和发送消息
js
// 定义一个使用者 新闻中心
var NewsCenter = {};
// 安装发布订阅模型
installEvent( NewsCenter );
// 客户在新闻中心订阅消息
NewsCenter.listen( '小明','科技新闻', function( msg ){ // 小明
console.log( msg[0] + ':' + msg[1] ); // 科技新闻=十条
});
NewsCenter.listen('小王', '娱乐新闻', function( msg ){ // 小王
console.log( msg[0] + ':' + msg[1] ); // 科技新闻=二十条
});
NewsCenter.listen('小张', '体育新闻', function( msg ){ // 小张
console.log( msg[0] + ':' + msg[1] );// 没有该消息 不会发布
});
// 取消订阅
NewsCenter.remove('小王', '娱乐新闻', function( msg ){ // 小王取消订阅
console.log(msg); // 科技新闻=十条
});
// 新闻中心发布消息
NewsCenter.trigger( '科技新闻', '十条' );
NewsCenter.trigger( '娱乐新闻', '二十条' );TIP
- 推模型与拉模型:
推模型:发布的状态和发布的内容同时告诉订阅者
拉模型:只将发布的状态通知给订阅者。发布的内容需要订阅者使用其他方法再获取
11.6 命令模式
命令模式:命令是对命令的封装,每一个命令都是一个操作,请求方发出请求,接收方接收请求,并执行操作
html
<body>
<div id="ball" style="position:absolute;background:#000;width:50px;height:50px"></div>
输入小球移动后的位置:<input id="pos"/>
<button id="moveBtn">开始移动</button>
<button id="cancelBtn">回到原点</button>
</body>js
// 操作小球往复运动
var ball = document.getElementById( 'ball' );// 要运动的小球元素
var pos = document.getElementById( 'pos' ); // 滚动的距离
var moveBtn = document.getElementById( 'moveBtn' ); // 开始运动
var cancelBtn = document.getElementById( 'cancelBtn' ); // 回到原点
var MoveCommand = function( receiver, pos ){
this.receiver = receiver;
this.pos = pos;
this.oldPos = null;
};
MoveCommand.prototype.execute = function(){
// 向左运动pos的长度
this.receiver.start( 'left', this.pos, 1000, 'strongEaseOut' );
// 记录小球开始移动前的位置
this.oldPos = this.receiver.dom.getBoundingClientRect()[ this.receiver.propertyName ];
};
MoveCommand.prototype.undo = function(){
// 回到小球移动前记录的位置
this.receiver.start( 'left', this.oldPos, 1000, 'strongEaseOut' );
};
var moveCommand;
moveBtn.onclick = function(){
var animate = new Animate( ball );
moveCommand = new MoveCommand( animate, pos.value );
moveCommand.execute();
};
cancelBtn.onclick = function(){
moveCommand.undo(); // 撤销命令
};11.7 组合模式
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性
js
// 扫描文件夹
/******************************* 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 );11.8 模板方法模式
模板方法模式:模板方法模式是一种只需使用继承就可以实现的非常简单的模式
js
var Coffee = function(){};
Coffee.prototype.boilWater = function(){
console.log( '把水煮沸' );
};
Coffee.prototype.brewCoffeeGriends = function(){
console.log( '用沸水冲泡咖啡' );
};
Coffee.prototype.pourInCup = function(){
console.log( '把咖啡倒进杯子' );
};
Coffee.prototype.addSugarAndMilk = function(){
console.log( '加糖和牛奶' );
};
Coffee.prototype.init = function(){
this.boilWater();
this.brewCoffeeGriends();
this.pourInCup();
this.addSugarAndMilk();
};
var coffee = new Coffee();
coffee.init();11.9 享元模式
享元模式:用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式
- 优点:大大减少对象的创建,降低系统的内存,使效率提高
- 缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱
- 使用场景:
- 内部状态存储于对象内部
- 内部状态可以被一些对象共享
- 内部状态独立于具体的场景,通常不会改变
- 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
js
// 有50种男式内衣和50种女士内衣,需要50个男模特50个女模特分别传上衣服牌子
var Model = function( sex, underwear){
this.sex = sex;
this.underwear= underwear;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
for ( var i = 1; i <= 50; i++ ){
var maleModel = new Model( 'male', 'underwear' + i );
maleModel.takePhoto();
};
for ( var j = 1; j <= 50; j++ ){
var femaleModel= new Model( 'female', 'underwear' + j );
femaleModel.takePhoto();
};
/* 要得到一张照片,每次都需要传入sex和underwear参数,
如上所述,现在一共有50种男内衣和50种女内衣,所以一共会产生100个对象*/- 通过享元模式简化
js
var Model = function( sex ){
this.sex = sex;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
// 分别创建一个男模特对象和一个女模特对象:
var maleModel = new Model( 'male' ),
femaleModel = new Model( 'female' );
// 给男模特依次穿上所有的男装,并进行拍照:
for ( var i = 1; i <= 50; i++ ){
maleModel.underwear = 'underwear' + i;
maleModel.takePhoto();
};
// 同样,给女模特依次穿上所有的女装,并进行拍照:
for ( var j = 1; j <= 50; j++ ){
femaleModel.underwear = 'underwear' + j;
femaleModel.takePhoto();
};
/* 性别是内部状态,内衣是外部状态,通过区分这两种状态,大大减少了系统中的对象数量
可以看到,改进之后的代码,只需要两个对象便完成了同样的功能 */11.10 职责链模式
职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止,请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。
js
/*
orderType:订单类型,1的时候是500元定金用户,2是200元定金用户,3是普通购买用户
pay:用户是否已经支付定金,定金用户如果一直没有支付定金,只能降级进入普通购买模式
stock:表示普通用户可购买的手机库存,定金的用户不受此限制
*/
var order = function( orderType, pay, stock ){
if ( orderType === 1 ){ // 500 元定金购买模式
if ( pay === true ){ // 已支付定金
console.log( '500 元定金预购, 得到 100 优惠券' );
}else{ // 未支付定金,降级到普通购买模式
if ( stock > 0 ){ // 用于普通购买的手机还有库存
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}else if ( orderType === 2 ){ // 200 元定金购买模式
if ( pay === true ){
console.log( '200 元定金预购, 得到 50 优惠券' );
}else{
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}else if ( orderType === 3 ){
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
};
order( 1 , true, 500); // 输出: 500 元定金预购, 得到 100 优惠券- 通过职责链模式重构
js
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 元定金预购,得到 50 优惠券' );
}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 指定在链中的下一个节点
Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
};
// Chain.prototype.passRequest 传递请求给某个节点
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;
};
// 把3个订单函数分别包装成职责链的节点
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 ); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足11.11 中介者模式
中介者模式:就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系(迪米特法则) 
缺点:因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象
js
/*
模拟两个队伍的游戏对战,一方角色全部死亡则另一方胜利
*/
// 玩家的构造函数
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.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;
};
/* 中介对象
在playerDirector中开放一些接收消息的接口,各player可以直接调用该接口来给playerDirector发送消息,
player只需传递一个参数给playerDirector,这个参数的目的是使playerDirector可以识别发送者。
同样,playerDirector接收到消息之后会将处理结果反馈给其他player
*/
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.playerDead(player)
};
// 玩家换队
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(); // 其他队伍所有玩家 win
}
}
}
}
};
var reciveMessage = function(){
var message = Array.prototype.shift.call( arguments ); // 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.changeTeam();js
/*
根据手机的颜色、内存和用户的购买数量判断是否可以购买
*/
var goods = { // 手机库存
"red|32G": 3,
"red|16G": 0,
"blue|32G": 1,
"blue|16G": 6
};
var mediator = (function(){
var colorSelect = document.getElementById( 'colorSelect' ),
memorySelect = document.getElementById( 'memorySelect' ),
numberInput = document.getElementById( 'numberInput' ),
colorInfo = document.getElementById( 'colorInfo' ),
memoryInfo = document.getElementById( 'memoryInfo' ),
numberInfo = document.getElementById( 'numberInfo' ),
nextBtn = document.getElementById( 'nextBtn' );
return {
changed: function( obj ){
var color = colorSelect.value, // 颜色
memory = memorySelect.value,// 内存
number = numberInput.value, // 数量
stock = goods[ color + '|' + memory ]; // 颜色和内存对应的手机库存数量
if ( obj === colorSelect ){ // 如果改变的是选择颜色下拉框
colorInfo.innerHTML = color;
}else if ( obj === memorySelect ){
memoryInfo.innerHTML = memory;
}else if ( obj === numberInput ){
numberInfo.innerHTML = number;
}
if ( !color ){
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择手机颜色';
return;
}
if ( !memory ){
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择内存大小';
return;
}
if ( ( ( number - 0 ) | 0 ) !== number - 0 ){ // 输入购买数量是否为正整数
nextBtn.disabled = true;
nextBtn.innerHTML = '请输入正确的购买数量';
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = '放入购物车';
}
}
})();
// 修改颜色
colorSelect.onchange = function(){
mediator.changed( this );
};
// 修改内存
memorySelect.onchange = function(){
mediator.changed( this );
};
// 购买数量
numberInput.oninput = function(){
mediator.changed( this );
};11.12 修饰者模式
装饰者模式:装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象(相当于式将一个对象嵌入另一个对象之中)
优点:使用继承的方式一方面会导致超类和子类之间存在强耦合性,其次在完成一些功能复用时,有可能创建出大量的子类,使子类的数量呈爆炸性增。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式
js
// 通过保存原引用的方式改写某个函数
var a = function(){
alert (1);
}
var _a = a;
a = function(){
_a(); //
alert (2);
}
a();- AOP装饰函数:基于原型(prototype)
js
// 新增函数在原函数 前执行
Function.prototype.before = function (beforefn) {
var __self = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的"代理"函数
beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
// 也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果
// 并且保证 this 不被劫持
}
}
// 新增函数在原函数 后执行
Function.prototype.after = function (afterfn) {
var __self = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的"代理"函数
var ret = __self.apply(this, arguments); // 执行原函数并记录原函数的执行结果
afterfn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
return ret; // 返回原函数的执行结果
}
};
document.getElementById = document.getElementById.before(function () {
console.log('before');
});
document.getElementById = document.getElementById.after(function () {
console.log('after');
});
var button = document.getElementById('button');
console.log(button);- AOP装饰函数:基于回调函数(callback)
js
var before = function (fn, beforefn) {
return function () {
beforefn.apply(this, arguments);
return fn.apply(this, arguments);
}
}
var after = function (fn, afterfn) {
return function () {
var ret = fn.apply(this, arguments);
afterfn.apply(this, arguments);
return ret
}
}
// 原函数
function a() {
console.log('a')
}
a = before(a, function () { console.log('before') });
a = after(a, function () { console.log('after') });
a(); /// before a afterAOP模式
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,是OOP(面向对象编程)的补充和完善。 简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
代理模式和装饰者模式的区别
代理模式是为其他对象提供一种代理以控制对这个对象的访问。装饰者模式是动态地给一个对象添加一些额外的职责。代理模式是行为的扩展,装饰者模式是对象的扩展。
11.13 状态模式
状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类 使用场景:
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态来改变他的行为。
- 代码中包含了大量与对象状态有关的条件语句时。
常规方法—文件上传
js
window.external.upload = function (state) {
console.log(state); // 可能为 sign、uploading、done、error
};
var plugin = (function () {
var plugin = document.createElement('embed');
plugin.style.display = 'none';
plugin.type = 'application/txftn-webkit';
plugin.sign = function () {
console.log('开始文件扫描');
}
plugin.pause = function () {
console.log('暂停文件上传');
};
plugin.uploading = function () {
console.log('开始文件上传');
};
plugin.del = function () {
console.log('删除文件上传');
}
plugin.done = function () {
console.log('文件上传完成');
}
document.body.appendChild(plugin);
return plugin;
})();
var Upload = function (fileName) {
this.plugin = plugin;
this.fileName = fileName;
this.button1 = null;
this.button2 = null;
this.state = 'sign'; // 设置初始状态为 waiting
};
Upload.prototype.init = function () {
var that = this;
this.dom = document.createElement('div');
this.dom.innerHTML =
'<span>文件名称:' + this.fileName + '</span>\
<button data-action="button1">扫描中</button>\
<button data-action="button2">删除</button>';
document.body.appendChild(this.dom);
this.button1 = this.dom.querySelector('[data-action="button1"]'); // 第一个按钮
this.button2 = this.dom.querySelector('[data-action="button2"]'); // 第二个按钮
this.bindEvent();
};
Upload.prototype.bindEvent = function () {
var self = this;
this.button1.onclick = function () {
if (self.state === 'sign') { // 扫描状态下,任何操作无效
console.log('扫描中,点击无效...');
} else if (self.state === 'uploading') { // 上传中,点击切换到暂停
self.changeState('pause');
} else if (self.state === 'pause') { // 暂停中,点击切换到上传中
self.changeState('uploading');
} else if (self.state === 'done') {
console.log('文件已完成上传, 点击无效');
} else if (self.state === 'error') {
console.log('文件上传失败, 点击无效');
}
};
this.button2.onclick = function () {
if (self.state === 'done' || self.state === 'error'
|| self.state === 'pause') {
// 上传完成、上传失败和暂停状态下可以删除
self.changeState('del');
} else if (self.state === 'sign') {
console.log('文件正在扫描中,不能删除');
} else if (self.state === 'uploading') {
console.log('文件正在上传中,不能删除');
}
};
};
Upload.prototype.changeState = function (state) {
switch (state) {
case 'sign':
this.plugin.sign();
this.button1.innerHTML = '扫描中,任何操作无效';
break;
case 'uploading':
this.plugin.uploading();
this.button1.innerHTML = '正在上传,点击暂停';
break;
case 'pause':
this.plugin.pause();
this.button1.innerHTML = '已暂停,点击继续上传';
break;
case 'done':
this.plugin.done();
this.button1.innerHTML = '上传完成';
break;
case 'error':
this.button1.innerHTML = '上传失败';
break;
case 'del':
this.plugin.del();
this.dom.parentNode.removeChild(this.dom);
console.log('删除完成');
break;
}
this.state = state;
};
var uploadObj = new Upload('JavaScript 设计模式与开发实践');
uploadObj.init();
window.external.upload = function (state) { // 插件调用 JavaScript 的方法
uploadObj.changeState(state);
};
window.external.upload('sign'); // 文件开始扫描
setTimeout(function () {
window.external.upload('uploading'); // 1 秒后开始上传
}, 1000);
setTimeout(function () {
window.external.upload('done'); // 5 秒后上传完成
}, 5000);状态模式重构文件上传程序
js
window.external.upload = function( state ){
console.log( state ); // 可能为 sign、uploading、done、error
};
var plugin = (function(){
var plugin = document.createElement( 'embed' );
plugin.style.display = 'none';
plugin.type = 'application/txftn-webkit';
plugin.sign = function(){
console.log( '开始文件扫描' );
}
plugin.pause = function(){
console.log( '暂停文件上传' );
};
plugin.uploading = function(){
console.log( '开始文件上传' );
};
plugin.del = function(){
console.log( '删除文件上传' );
}
plugin.done = function(){
console.log( '文件上传完成' );
}
document.body.appendChild( plugin );
return plugin;
})();11.14 适配器模式
适配器模式:就是使两个不兼容的接口通过适配器进行兼容。
js
// 当我们向 googleMap 和 baiduMap 都发出“显示”请求时两者提供的方法不一致
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};
var baiduMap = {
display: function(){
console.log( '开始渲染百度地图' );
}
};
// 百度需要调用display方法,因此为百度增加一个适配器
var baiduMapAdapter = {
show: function(){
return baiduMap.display();
}
};
// 统一使用show方法使 googleMap 和 baiduMap兼容
var renderMap = function( map ){
if ( map.show instanceof Function ){
map.show();
}
};