定义

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问

# 案例:小明给女神送花

小明遇到了女神A,决定给她送花表白。小明和A有一个共同的朋友B,于是内向的小明让B来代替自己送花。 假设当A在心情好的时候收到花,小明表白成功的几率有60%,而当A在心情差的时候收到花,小明表白的成功率无限趋近于0。 小明跟A刚刚认识两天,还无法辨别A什么时候心情好。如果不合时宜地把花送给A,花被直接扔掉的可能性很大,这束花可是小明吃了7天泡面换来的。 但是A的朋友B却很了解A,所以小明只管把花交给B,B会监听A的心情变化,然后选择A心情好的时候把花转交给A。

代码如下:

var Flower = function () {}

var xiaoming = {
  sendFlower: function (target) {
    target.receiveFlower(); 
  }
}

var B = {
  receiveFlower: function () {
    A.listenGoodMood(function () { 
      var flower = new Flower(); 
      A.receiveFlower(flower);
    }); 
  }
}

var A = {
  receiveFlower: function(flower){
    console.log( '收到花 ' + flower ); 
	},
	
  listenGoodMood: function(fn){
    setTimeout(function(){ // 假设 10 秒之后 A 的心情变好
      fn(); 
    }, 10000 );
  } 
}

xiaoming.sendFlower( B );

# 保护代理和虚拟代理

上面的例子可以引出两种代理模式:

保护代理

代理B可以帮助A过滤掉一些请求,比如送花的人中年龄太大的或者没有宝马的,这种请求就可以直接在代理B处被拒绝掉。

虚拟代理

假设现实中的花价格不菲,导致在程序世界里,new Flower 也是一个代价昂贵的操作,那么我们可以把 new Flower 的操作交给代理 B 去执行,代理 B 会选择在 A 心情好时再执行 new Flower

# 案例:虚拟代理实现图片预加载

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('file:// /C:/Users/svenzeng/Desktop/loading.gif');
			img.src = src;  
		}
  } 
})();
proxyImage.setSrc('http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');

通过proxyImage间接的访问MyImage,proxyImage控制了客户对MyImage的访问,并且在此过程中加入了一些额外的操作,图片被真正加载好之前,页面中会出现占位的loading图,提示用户图片正在加载。

# 代理和本体接口接口的一致性

  • 用户可以放心地请求代理,他只关心是否能得到想要的结果
  • 在任何使用本体的地方都可以替换成使用代理

# 缓存代理

计算乘积案例

var mult = function () {
  console.log("开始计算。。。");
  var a = 1;
  for (var i = 0, l = arguments.length; i < l; i++) {
    a *= arguments[i];
  }
  return a;
}

var proxyMult = (function () {
  var cache = {};
  return function () {
    var args = Array.prototype.join.call(arguments, ",");
    if (!cache[args]) {
      cache[args] = mult.apply(this, arguments);
    }
    return cache[args];
  }
})();

console.log(proxyMult(1, 2, 3, 4));
console.log(proxyMult(1, 2, 3, 4));

当我们第二次调用proxyMult(1, 2, 3, 4)的时候,本地mult函数并没有被计算,proxyMult直接返回了之前缓存好的计算结果。
通过增加缓存代理的方式,mult函数可以继续专注于自身的职责--计算乘积,缓存的功能是由代理对象实现的。

# 其他代理模式

  • 防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。
  • 远程代理:为一个对象在不同的地址空间提供局部代表
  • 保护代理:用于对象应该有不同访问权限的情况
  • 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数。
  • 写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作。

# 小结

代理模式包括许多小分类,在JavaScript开发中最常用的是虚拟代理和缓存代理。

# 心得体会

代理其实就是为对象添加新的行为,这些新的行为存在于代理对象中。被代理的对象只需要关注自身的职责。
这种模式可以让代理更好的实践“单一职责原则”