代理模式是为一个对象提供一个代用品或者占位符,以便控制对它的访问。
代理模式的关建是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际访问的是替身对象,替身对象对请求做出一些处理后,再把请求转交给本体对象。
先来看一个例子,假设A想送花给C,但又不知道C的心情,C的心情好时即送花成功,那么可以通过C的好朋友B来送花,B了解C,可以知道C的心情。
var Flower = function(){}; //A发起送花请求 var A = { sendFlower:function(target){ var flower = new Flower(); target.receiveFlower(flower); } }; //B接受送花请求 并监听到C的心情好时转给C var B = { receiveFlower:function(flower){ C.listenGoodMood(function(){ C.receiveFlower(flower); }); } }; //C接受送花请求 和 发表心情 var C = { receiveFlower :function(flower){ console.log(flower); }, listenGoodMood:function(fn){ setTimeout(function(){ fn(); },1000) } }; A.sendFlower(B);
在实际开发中,由于花的价格昂贵,那么可以当B监听到C的好心情时,才创建花来节省消耗。
var B = { receiveFlower:function(){ C.listenGoodMood(function(){ var flower = new Flower(); C.receiveFlower(flower); }); } };
在这个例子中,最关键的就是B对象,也就是该章描述的代理对象,代理B可以帮助C过滤掉那些年龄太大之类的人,这些请求可以被B直接拒绝掉,这种被称为保护代理。而在真正需要的时候才来创建对象,也就是在C心情好的时候才创建花,被称为虚拟代理。
下面来看一个实际开发中的例子,通常在加载一个比较大的图片时,我们会先用一张菊花图提示用户化正在加载,当真正的图片被加载完成后,才显示该图片。
//加载图片的类 var myImg = (function(){ var imgNode = document.createElement('img'); document.body.appendChild(imgNode); return { setSrc:function(src){ imgNode.src = src; } } })(); //代理类,在图片加载完成之前 显示菊花图 加载完成后 改换真正的图片 var proxyImg = (function(){ var img = new Image(); img.onload = function(){ myImg.setSrc(this.src); }; return { setSrc:function(src){ myImg.setSrc("菊花图!"); img.src = src; } } })(); proxyImg.setSrc('真正的图片!');
在这里,也许会有疑问,即使不用代理模式也能实现这种效果,先来看一下不用代理的代码实现:
var myImg = (function(){ var imgNode = document.createElement('img'); document.body.appendChild(imgNode); var img = new Image(); img.onload = function(){ myImg.setSrc(this.src); }; return { setSrc:function(src){ myImg.setSrc("菊花图!"); img.src = src; } } })(); myImg.setSrc("真正的图片!")
为了说明代理的意义,先引入一个概念,即单一职责原则,表示一个类应该只有一个引起它变化的原因。这保证了类的高内聚和低耦合。
在不使用代理的例子下,myImg负责了给img设置src之外,还要负责预加载的图片,有时候我们的图片可能很小,或者以后的网速很快,那么我们就需要深入到该类来删除预加载的功能。
而如果采用了代理模式,那么当我们不需要预加载时,只需要直接调用myImg就可以了。
虚拟代理在惰性加载中的应用。
如一个控制台,当用户输入console.log(0)时,会在控制台打印0,但通常用户并不会一开始就打开控制台,所以也不会一开始就加载控制台。那么我们可以先创建一个代理类,将要打印的信息放进该代理类,当用户按下F12打开控制台的时候,我们创建了控制台之后,再遍历这些信息并输出。
代理在zepto类库事件处理中使用得比较多,具体可以参考zepto事件分析系列,其中带有proxy的即为代理对象。
参考自Javascript设计模式与开发实践一书。