JavaScript 设计模式(中)——3.代理模式

3 代理模式

代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式分为保护代理和虚拟代理,保护代理用于控制不同权限的对象对目标对象的访问,虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建;

3.1 虚拟代理实现图片预加载

  1. 虚拟代理实现图片预加载
    图片预加载中先用一张 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里,这种场景就很适合使用虚拟代理;
var myImage = (function(){
  var imgNode = document.createElement( 'img' );
  document.body.appendChild( imgNode );
    return {
      setSrc: function( src ){
      imgNode.src = src;
    }
  }
})();
// 代理对象,在图片被真正加载好之前,页面中将出现一张占位的图 loading.gif, 来提示图片正在加载
var proxyImage = (function(){
  var img = new Image;
  img.onload = function(){
    myImage.setSrc( this.src );
  }
  return {
    setSrc: function( src ){
      myImage.setSrc( 'file:///C:/Users/admin/Desktop/loading.gif' );
      img.src = src;
    }
  }
})();

proxyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
  1. 不用代理的预加载图片函数实现:
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 = 'file:// /C:/Users/svenzeng/Desktop/loading.gif';
      img.src = src;
    }
  }
})();
MyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

上段代码中的 MyImage 对象除了负责给 img 节点设置 src 外,还要负责预加载图片。在处理其中一个职责时,有可能因为其强耦合性影响另外一个职责的实现;在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放—封闭原则;假如之后只是从网络上获取一些体积很小的图片,或者根本不再需要预加载,我们希望把预加载图片的这段代码从 MyImage 对象里删掉,这时候就不得不改动 MyImage 对象了。而实际上需要的只是给 img 节点设置 src,预加载图片只是一个锦上添花的功能。如果能把这个操作放在另一个对象里面,自然是一个非常好的方法;于是代理的作用在这里就体现出来了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体 MyImage ;

单一职责原则:就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏;

3.2 虚拟代理合并 HTTP 请求

使用代理函数合并 HTTP 请求实现:使用代理函数收集一段时间之内的请求,最后一次性发送给服务器;


  1
  2
  3

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 = 0; // 清空 ID 集合
    }, 2000 );
  }
})();

var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
  c.onclick = function(){
    if ( this.checked === true ){
      proxySynchronousFile( this.id );
    }
  }
};

3.3 缓存代理

缓存代理实例:计算乘积;

// 乘积函数
var mult = function(){
  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 );
  }
})();
proxyMult( 1, 2, 3, 4 ); // 输出: 24
proxyMult( 1, 2, 3, 4 ); // 输出: 24

缓存代理用于ajax异步请求数据

在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。

3.4 高阶函数动态创建代理

实例:为乘法、加法、减法等创建缓存代理:

/**************** 计算乘积 *****************/
var mult = function(){
var a = 1;
  for ( var i = 0, l = arguments.length; i < l; i++ ){ a = a * arguments[i]; }
  return a;
};
/**************** 计算加和 *****************/
var plus = function(){
var a = 0;
  for ( var i = 0, 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( 1, 2, 3, 4 ) ); // 输出: 24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出: 10

3.5 其他代理模式的应用

  • 防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。
  • 远程代理:为一个对象在不同的地址空间提供局部代表,在 Java 中,远程代理可以是另一个虚拟机中的对象。
  • 保护代理:用于对象应该有不同访问权限的情况。
  • 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数。
  • 写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体, DLL(操作系统中的动态链接库)是其典型运用场景。

3.6 代理模式小结

代理模式在 JavaScript 开发中最常用的是虚拟代理和缓存代理。一般在编写代码的时候不需要去预先猜测是否需要使用代理模式。当真正发现不方便直接访问某个对象的时候,再编写代理模式代码。

系列链接

  1. JavaScript 设计模式(上)——基础知识
  2. JavaScript 设计模式(中)——1.单例模式
  3. JavaScript 设计模式(中)——2.策略模式
  4. JavaScript 设计模式(中)——3.代理模式
  5. JavaScript 设计模式(中)——4.迭代器模式
  6. JavaScript 设计模式(中)——5.发布订阅模式
  7. JavaScript 设计模式(中)——6.命令模式
  8. JavaScript 设计模式(中)——7.组合模式
  9. JavaScript 设计模式(中)——8.模板方法模式
  10. JavaScript 设计模式(中)——9.享元模式
  11. JavaScript 设计模式(中)——10.职责链模式
  12. JavaScript 设计模式(中)——11. 中介者模式
  13. JavaScript 设计模式(中)——12. 装饰者模式
  14. JavaScript 设计模式(中)——13.状态模式
  15. JavaScript 设计模式(中)——14.适配器模式
  16. JavaScript 设计模式(下)——设计原则
  17. JavaScript 设计模式练习代码

本文主要参考了《JavaScript设计模式和开发实践》一书

你可能感兴趣的:(JavaScript 设计模式(中)——3.代理模式)