javascript设计模式——代理模式
前面的话
代理模式是为一个对象提供一个占位符,以便控制对它的访问。 代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景。比如,明星都有经纪人作为代理。如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演出的细节和报酬都谈好之后,再把合同交给明星签。 代理模式的关键是当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。本文将详细介绍代理模式
代理模式结构
比如,实现一个小明让B代替自己向A送花的功能。首先,不引入代理,小明直接送花给A,代码如下
var Flower = function(){};
var xiaoming = {
sendFlower: function( target ){
var flower = new Flower();
target.receiveFlower( flower );
}
};
var A = {
receiveFlower: function( flower ){
console.log( '收到花 ' + flower );
}
};
xiaoming.sendFlower( A );
接下来,引入代理 B,即小明通过 B 来给 A 送花
var Flower = function(){};
var xiaoming = {
sendFlower: function( target){
var flower = new Flower();
target.receiveFlower( flower );
}
};
var B = {
receiveFlower: function( flower ){
A.receiveFlower( flower );
}
};
var A = {
receiveFlower: function( flower ){
console.log( '收到花 ' + flower );
}
};
xiaoming.sendFlower( B );
现在改变故事的背景设定,假设当A在心情好的时候收到花,小明表白成功的几率有60%,而当A在心情差的时候收到花,小明表白的成功率无限趋近于0。小明跟A刚刚认识两天,还无法辨别A什么时候心情好。如果不合时宜地把花送给A,花被直接扔掉的可能性很大。但是A的朋友B却很了解A,所以小明只管把花交给B,B会监听A的心情变化,然后选择A心情好的时候把花转交给A,代码如下:
var Flower = function(){};
var xiaoming = {
sendFlower: function( target){
var flower = new Flower();
target.receiveFlower( flower );
}
};
var B = {
receiveFlower: function( flower ){
A.listenGoodMood(function(){ // 监听A的好心情
A.receiveFlower( flower );
});
}
};
var A = {
receiveFlower: function( flower ){
console.log( '收到花 ' + flower );
},
listenGoodMood: function( fn ){
setTimeout(function(){ // 假设10秒之后A的心情变好
fn();
}, );
}
};
xiaoming.sendFlower( B );
虽然这只是个虚拟的例子,但可以从中找到两种代理模式的身影
【保护代理】
代理B可以帮助A过滤掉一些请求,比如送花的人中年龄太大的或者没有宝马的,这种请求就可以直接在代理B处被拒绝掉。这种代理叫作保护代理
【虚拟代理】
把newFlower的操作交给代理B去执行,代理B会选择在A心情好时再执行newFlower,这是代理模式的另一种形式,叫作虚拟代理。虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。代码如下:
var B = {
receiveFlower: function( flower ){
A.listenGoodMood(function(){ // 监听A 的好心情
A.receiveFlower( flower ); //延迟创建flower对象
});
}
};
图片预加载
在Web开发中,图片预加载是一种常用的技术,如果直接给某个img标签节点设置src属性,由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张loading图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里,这种场景就很适合使用虚拟代理
下面来实现这个虚拟代理,首先创建一个普通的本体对象,这个对象负责往页面中创建一个img标签,并且提供一个对外的setSrc接口,外界调用这个接口,便可以给该img标签设置
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
imgNode.src = src;
}
}
})();
myImage.setSrc( 'https://static.xiaohuochai.site/icon/icon_200.png' );
现在开始引入代理对象proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中将出现一张占位的loading.gif,来提示用户图片正在加载
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.gif' );
img.src = src;
}
}
})();
proxyImage.setSrc( 'https://static.xiaohuochai.site/icon/icon_200.png' );
现在通过proxyImage间接地访问myImage。proxyImage控制了客户对myImage的访问,并且在此过程中加入一些额外的操作,比如在真正的图片加载好之前,先把img节点的src设置为一张本地的loading图片
单一职责原则
如果不使用代理,则图片预加载的函数实现代码如下
var MyImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
var img = new Image;
img.onload = function(){
imgNode.src = img.src;
};
return {
setSrc: function( src ){
imgNode.src = 'loading.gif';
img.src = src;
}
}
})();
MyImage.setSrc( 'https://static.xiaohuochai.site/icon/icon_200.png' );
下面引入一个面向对象设计的原则——单一职责原则
单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏
职责被定义为“引起变化的原因”。上面代码中的MyImage对象除了负责给img节点设置src外,还要负责预加载图片。在处理其中一个职责时,有可能因为其强耦合性影响另外一个职责的实现
另外,在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放——封闭原则。如果只是从网络上获取一些体积很小的图片,或者5年后的网速快到根本不再需要预加载,可能希望把预加载图片的这段代码从MyImage对象里删掉。这时候就不得不改动 MyImage 对象了
实际上,需要的只是给img节点设置src,预加载图片只是一个锦上添花的功能。如果能把这个操作放在另一个对象里面,自然是一个非常好的方法。于是代理的作用在这里就体现出来了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体MyImage
纵观整个程序,并没有改变或者增加MyImage的接口,但是通过代理对象,实际上给系统添加了新的行为。这是符合开放——封闭原则的。给img节点设置 src 和图片预加载这两个功能, 被隔离在两个对象里,它们可以各自变化而不影响对方。何况就算有一天不再需要预加载, 那么只需要改成请求本体而不是请求代理对象即可
【代理和本体接口的一致性】
代理对象和本体都对外提供了setSrc方法,在客户看来,代理对象和本体是一致的, 代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别,这样做有两个好处:1、用户可以放心地请求代理,只关心是否能得到想要的结果;2、在任何使用本体的地方都可以替换成使用代理
如果代理对象和本体对象都为一个函数(函数也是对象),函数必然都 能被执行,则可以认为它们也具有一致的“接口”
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 );
}
return function( src ){
myImage( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
img.src = src;
}
})();
proxyImage( 'https://static.xiaohuochai.site/icon/icon_200.png' );
合并HTTP请求
在Web开发中,也许最大的开销就是网络请求。假设在做一个文件同步的功能,选中一个checkbox时,它对应的文件就会被同步到另外一台备用服务器上面
首先,在页面中放置好这些checkbox节点
<body>
<input type="checkbox" id=""></input>
<input type="checkbox" id=""></input>
<input type="checkbox" id=""></input>
<input type="checkbox" id=""></input>
<input type="checkbox" id=""></input>
<input type="checkbox" id=""></input>
<input type="checkbox" id=""></input>
<input type="checkbox" id=""></input>
<input type="checkbox" id=""></input>
</body>
接下来,给这些checkbox绑定点击事件,并且在点击的同时往另一台服务器同步文件
var synchronousFile = function( id ){
console.log( '开始同步文件,id 为: ' + id );
};
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = , c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
synchronousFile( this.id );
}
}
};
当选中3个checkbox的时候,依次往服务器发送了3次同步文件的请求。可以预见,如此频繁的网络请求将会带来相当大的开销。解决方案是可以通过一个代理函数proxySynchronousFile来收集一段时间之内的请求,最后一次性发送给服务器。比如等待2秒之后才把这2秒之内需要同步的文件ID打包发给服务器,如果不是对实时性要求非常高的系统,2秒的延迟不会带来太大副作用,却能大大减轻服务器的压力
var synchronousFile = function( id ){
console.log( '开始同步文件,id 为: ' + id );
};
var proxySynchronousFile = (function(){
var cache = [], // 保存一段时间内需要同步的ID
timer; // 定时器
return function( id ){
cache.push( id );
if ( timer ){ // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒后向本体发送需要同步的ID 集合
clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = ; // 清空ID 集合
}, );
}
})();
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = , c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};
虚拟代理在惰性加载中的应用
假设要加载miniConsole.js这个文件,该文件用于打印log,但该文件很大。所以,通常解决方案是用一个占位的miniConsole代理对象来给用户提前使用,然后将打印log的请求都包裹在一个函数里,随后这些函数将全部放到缓存队列中,这些逻辑都是在miniConsole代理对象中完成实现的。等用户按下F2唤出控制台的时候,才开始加载真正的miniConsole.js的代码,加载完成之后将遍历miniConsole代理对象中的缓存函数队列,同时依次执行它们
未加载真正的miniConsole.js之前的代码如下:
var cache = [];
var miniConsole = {
log: function(){
var args = arguments;
cache.push( function(){
return miniConsole.log.apply( miniConsole, args );
});
}
};
miniConsole.log();
当用户按下F2时,开始加载真正的miniConsole.js,代码如下:
var handler = function( ev ){
if ( ev.keyCode === ){
var script = document.createElement( 'script' );
script.onload = function(){
for ( var i = , fn; fn = cache[ i++ ]; ){
fn();
}
};
script.src = 'miniConsole.js';
document.getElementsByTagName( 'head' )[].appendChild( script );
}
};
document.body.addEventListener( 'keydown', handler, false );
// miniConsole.js 代码:
miniConsole = {
log: function(){
// 真正代码略
console.log( Array.prototype.join.call( arguments ) );
}
};
要注意的是,要保证在F2被重复按下时,miniConsole.js只被加载一次
var miniConsole = (function(){
var cache = [];
var handler = function( ev ){
if ( ev.keyCode === ){
var script = document.createElement( 'script' );
script.onload = function(){
for ( var i = , fn; fn = cache[ i++ ]; ){
fn();
}
};
script.src = 'miniConsole.js';
document.getElementsByTagName( 'head' )[].appendChild( script );
document.body.removeEventListener( 'keydown', handler );// 只加载一次miniConsole.js
}
};
document.body.addEventListener( 'keydown', handler, false );
return {
log: function(){
var args = arguments;
cache.push( function(){
return miniConsole.log.apply( miniConsole, args );
});
}
}
})();
miniConsole.log( ); // 开始打印log
// miniConsole.js 代码
miniConsole = {
log: function(){
// 真正代码略
console.log( Array.prototype.join.call( arguments ) );
}
}
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果
下面是一个计算乘积的例子
先创建一个用于求乘积的函数:
var mult = function(){
console.log( '开始计算乘积' );
var a = ;
for ( var i = , l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
mult( , ); // 输出:6
mult( , , ); // 输出:24
然后加入缓存代理函数
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 );
}
})();
proxyMult( , , , ); // 输出:24
proxyMult( , , , ); // 输出:24
当第二次调用proxyMult(1,2,3,4)的时候,本体mult函数并没有被计算,proxyMult直接返回了之前缓存好的计算结果。通过增加缓存代理的方式,mult函数可以继续专注于自身的职责——计算乘积,缓存的功能是由代理对象实现的
在项目中常常遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。显然这里也可以引入缓存代理,实现方式跟计算乘积的例子差不多,唯一不同的是,请求数据是个异步的操作,无法直接把计算结果放到代理对象的缓存中,而是要通过回调的方式
动态创建代理
通过传入高阶函数的方式,可以为各种计算方法创建缓存代理。现在这些计算方法被当作参数传入一个专门用于创建缓存代理的工厂中,这样一来,就可以为乘法、加法、减法等创建缓存代理,代码如下:
/**************** 计算乘积 *****************/
var mult = function(){
var a = ;
for ( var i = , l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
/**************** 计算加和 *****************/
var plus = function(){
var a = ;
for ( var i = , l = arguments.length; i < l; i++ ){
a = a + arguments[i];
}
return a;
};
/**************** 创建缓存代理的工厂 *****************/
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 proxyMult = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
alert ( proxyMult( , , , ) ); // 输出:24
alert ( proxyMult( , , , ) ); // 输出:24
alert ( proxyPlus( , , , ) ); // 输出:10
alert ( proxyPlus( , , , ) ); // 输出:10
其他代理模式
代理模式的变体种类非常多,还包括以下几种
1、防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近
2、远程代理:为一个对象在不同的地址空间提供局部代表
3、保护代理:用于对象应该有不同访问权限的情况
4、智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数
5、写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体,DLL(操作系统中的动态链接库)是其典型运用场景
代理模式包括许多小分类,在javascript开发中最常用的是虚拟代理和缓存代理。虽然代理模式非常有用,但在编写业务代码时,往往不需要去预先猜测是否需要使用代理模式。当真正发现不方便直接访问某个对象的时候,再编写代理也不迟
javascript设计模式——代理模式的更多相关文章
- JavaScript设计模式 - 代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问 代理模式的用处(个人理解):为了保障当前对象的单一职责(相对独立性),而需要创建另一个对象来处理调用当前对象之前的一些逻辑以提高代码的效 ...
- 读书笔记之 - javascript 设计模式 - 代理模式
代理(proxy)是一个对象,它可以用来控制对另一对象的访问.它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象.另外那个对象通常称为本体.代理可以代替本体被实例化,并使其可被远程访 ...
- JavaScript设计模式—代理模式
代理模式介绍 使用者无权访问目标对象,中间加代理,通过代理做授权和控制 代理(proxy)是一个对象,它可以用来控制对另外一个对象的访问: 代理对象和本体对象实现了同样的接口,并且会把任何方法调用传递 ...
- 9. 星际争霸之php设计模式--代理模式
题记==============================================================================本php设计模式专辑来源于博客(jymo ...
- C++设计模式——代理模式
前言 青春总是那样,逝去了才开始回味:大学生活也是在不经意间就溜走了,现在上班的时候,偶尔还会怀念大学时,大家在一起玩游戏的时光.大学喜欢玩游戏,但是可悲的校园网,速度能把人逼疯了:还好,后来搞了一个 ...
- PHP设计模式-代理模式
概念理解: 代理模式,是对简单处理程序(或指针)的增强,用于引用一个对象:这个指针被代理对象取代,代理对象位于客户端和真实程序之间,指针有一个可被多个目标利用的钩子. 参与者: client(参与者) ...
- Java设计模式-代理模式之动态代理(附源代码分析)
Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...
- 浅谈Python设计模式 - 代理模式
声明:本系列文章主要参考<精通Python设计模式>一书,并且参考一些资料,结合自己的一些看法来总结而来. 一.在某些应用中,我们想要在访问某个对象之前执行一个或者多个重要的操作,例如,访 ...
- Java 之 设计模式——代理模式
设计模式——代理模式 一.概述 1.代理模式 (1)真实对象:被代理的对象 (2)代理对象:代理真实对象的 (3)代理模式:代理对象代理真实对象,达到增强真实对象功能的目的 二.实现方式 1.静态代理 ...
随机推荐
- JDBC连接池-C池3P0连接
JDBC连接池-C3P0连接 c3p0连接池的学习英语好的看英文原版 c3p0 - JDBC3 Connection and Statement Pooling 使用c3p0连接池 三种方 ...
- 2456: mode
2456: mode Time Limit: 1 Sec Memory Limit: 1 MBSubmit: 4798 Solved: 2009[Submit][Status][Discuss] ...
- Exclusive-OR(带权并查集)
Exclusive-OR Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...
- 有道云翻译接口 Show类
package com.yangchong.fanyi; import java.awt.EventQueue;import java.awt.Toolkit; import javax.swing. ...
- 「设计模式」JavaScript - 设计模式之单例模式与场景实践
单例介绍 上次总结了设计模式中的module模式,可能没有真真正正的使用在场景中,发现效果并不好,想要使用起来却不那么得心应手, 所以这次我打算换一种方式~~从简单的场景中来看单例模式, 因为Java ...
- D3.js从入门到“放弃”指南
前言 近期略有点诸事不顺,趁略有闲余之时,玩起D3.js.之前实际项目中主要是用各种chart如hightchart.echarts等,这些图形库玩起来貌都是完美的,一切皆可配置,但几年前接触了D3之 ...
- javascript 二维(多维)数组的复制问题
最近在项目中遇到一个动画暂停的效果,需要在动画停止的时候检测当前坐标和已经运行的时间,从而调节时间轴为再次运行时加速. 但是在数组保存方面折腾了半天. var orbitArray = [], lin ...
- TIBCO EMS安装部署
创建用户 groupadd -g 800 tibcouseradd -u 801 -g tibco -d /home/tibco/ -s /bin/bash tibco 目前关于sharedatast ...
- Scrum Meeting Alpha - 3
Scrum Meeting Alpha - 3 NewTeam 2017/10/27 地点:新主楼F座二楼 任务反馈 团队成员 完成任务 计划任务 安万贺 找到了几个开源项目,参考了API的包装方式, ...
- Linux系列教程(十九)——Linux文件系统管理之手工分区
上篇博客我们首先介绍了硬盘为什么要分区,以及Linux系统的几种分区类型,然后介绍了Linux系统几个常用的文件系统命令,最后讲解了挂载命令,并通过实例演示了如何挂载光盘和U盘. 本篇博客我们将介绍l ...