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

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。

定义

代理,顾名思义就是帮助别人做事,GoF对代理模式的定义如下:

代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。

使用实例

简单实例

我们来举一个简单的例子,假如小明要送酸奶小妹玫瑰花,却不知道她的联系方式或者不好意思,想委托大叔去送这些玫瑰,那大叔就是个代理,那我们如何来做呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 先声明美女对象
var girl = function (name) {
    this.name = name;
};

// 这是小明
var xiaoMing = function (girl) {
    this.girl = girl;
    this.sendGift = function (gift) {
        alert("Hi " + girl.name + ", 小明送你一个礼物:" + gift);
    }
};

// 大叔是代理
var proxyTom = function (girl) {
    this.girl = girl;
    this.sendGift = function (gift) {
        (new xiaoMing(girl)).sendGift(gift); // 替dudu送花咯
    }
};

调用方式:

1
2
var proxy = new proxyTom(new girl("酸奶小妹"));
proxy.sendGift("999朵玫瑰");

虚拟代理实现图片预加载

在 Web 开发中,图片预加载是一种常用的技术,如果直接给某个 img 标签节点设置 src 属性, 由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里,这种 场景就很适合使用虚拟代理。

下面我们来实现这个虚拟代理,首先创建一个普通的本体对象,这个对象负责往页面中创建 一个 img 标签,并且提供一个对外的 setSrc 接口,外界调用这个接口,便可以给该 img 标签设置 src 属性:

1
2
3
4
5
6
7
8
9
var myImage = (function(){
	var imgNode = document.createElement( 'img' );
	document.body.appendChild( imgNode );
	return {
		setSrc: function( src ) {
			imgNode.src = src;
		}
	}
})();

我们把网速调至 5KB/s,然后通过 MyImage.setSrc 给该 img 节点设置 src,可以看到,在图片

被加载好之前,页面中有一段长长的空白时间。

现在开始引入代理对象 proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中 将出现一张占位的菊花图 loading.gif, 来提示用户图片正在加载。代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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( './pic.jpg' );
			img.src = src;
		}
	}
})();

proxyImage.setSrc( './loading.gif' );

总结

在 JavaScript 开发中最常用的是虚拟代理和缓存代理。虽然代理模式非常有用,但我们在编写业务代码的时候,往往不需要去预先猜测是否需要使用代理模式。 当真正发现不方便直接访问某个对象的时候,再编写代理也不迟。


参考引用资料

汤姆大叔的博客——深入理解JavaScript系列