概念
代理模式由于一个对象不能直接引用另外一个对象,所以需要通过代理来让两个对象之间起到中介的作用。
比如 Vue 中的响应式设计,我们不能去操作你的原生对象一样,实际上去操作 Vue 内部实现的代理对象。如果去直接操作原对象,数据和节点之间就不能存在响应。
所以代理模式 的本意就是当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问。客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
虚拟代理
虚拟代理是为了解决一个对象直接生成的代价太过于昂贵,利用其他对象对他一部分功能进行代理,达到优化的效果。
比如我们去实现一个图片预加载的时候,如果我们的图片很大,用户就会看到页面很长一段时间是空白 我们可以想到的改进是图片加载完成之前都展示loading图片。
var CreateImage = ( function() {
var ImageNode = document.createElement("img")
document.body.appendChild( ImageNode )
var img = new Image()
// 模拟图片加载
img.onload = function() {
setTimeout( function() {
ImageNode.src = img.src
},1000 )
}
return {
setSrc:function( src ) {
img.src = src
ImageNode.src = "https://hbimg.b0.upaiyun.com/efd962de4532ee6b867e4938cfdba8cf44b87d27416a4-0yryg5_fw658"
}
}
} )()
CreateImage.setSrc( "https://picx.zhimg.com/v2-821962f0ca11d1b9ac1d73397e21bd87_l.jpg?source=32738c0c" )
可以看到,上面的代码中 CreateImage 对象同时承担了加载图片和预加载图片的两种职责,大大增加了对象生成的代价。同时也违背了开放封闭原则,如果我们以后不需要预加载图片了,那我们不得不修改整个对象。
我们可以使用 虚拟代理 来对代码进行改性,减少对象生成代价,同时也符合符合代码职责单一原则。
var CreateImage = (function () {
var ImageNode = document.createElement("img")
document.body.appendChild(ImageNode)
return {
setSrc: function (src) {
ImageNode.src = src
}
}
})()
var ProxyImage = (function () {
var img = new Image()
// 模拟图片加载
img.onload = function () {
setTimeout(function () {
CreateImage.setSrc( img.src )
}, 1000)
}
return {
setSrc: function( src ) {
img.src = src
CreateImage.setSrc( "https://hbimg.b0.upaiyun.com/efd962de4532ee6b867e4938cfdba8cf44b87d27416a4-0yryg5_fw658" )
}
}
})()
ProxyImage.setSrc("https://picx.zhimg.com/v2-821962f0ca11d1b9ac1d73397e21bd87_l.jpg?source=32738c0c")
虚拟代理合并HTTP请求
假设我们在做一个文件同步的功能,当我们选中一个checkbox的时候,它对应的文件就会被同步到另外一台服务器上面,此时,我们的思路就是当checkbox被选中时,把选中的文件传到另一台服务器。代码如下:
<body>
<input type="checkbox" id="1"></input>1
<input type="checkbox" id="2"></input>2
<input type="checkbox" id="3"></input>3
<input type="checkbox" id="4"></input>4
<input type="checkbox" id="5"></input>5
<input type="checkbox" id="6"></input>6
<input type="checkbox" id="7"></input>7
<input type="checkbox" id="8"></input>8
<input type="checkbox" id="9"></input>9
</body>
function synchronousFile(id) {
console.log("开始同步文件,id为:" + id);
}
var checkboxElements = document.getElementsByTagName("input")
var length = checkboxElements.length
for (var i = 0; i < length; i++) {
(function () {
var ele = checkboxElements[i]
ele.addEventListener("click", function () {
synchronousFile(ele.getAttribute("id"))
})
})()
}
虽然现在功能已经实现了,但是问题还存在问题,当我们去每次选择的时候,都会调用 synchronousFile 函数,并发送 http 请求,这会带来极大的消耗。
function synchronousFile(id) {
console.log("开始同步文件,id为:" + id);
}
var checkboxElements = document.getElementsByTagName("input")
var length = checkboxElements.length
for (var i = 0; i < length; i++) {
(function () {
var ele = checkboxElements[i]
ele.addEventListener("click", function () {
proxySynchronousFile(ele.getAttribute("id"))
})
})()
}
// 收集一段时间的请求
var proxySynchronousFile = (function () {
var cacheIds = [], timeId = 0
return function( id ) {
if( cacheIds.indexOf( id ) < 0 ) {
cacheIds.push( id )
}
clearTimeout( timeId )
timeId = setTimeout( function() {
synchronousFile( cacheIds.join(",") )
cacheIds = []
},1000 )
}
})()
通过 proxySynchronousFile 代理函数,收集一段时间内的请求,最后统一发给服务器,如果不是实时性要求很高的系统,有一点延迟并不会带来太大的副作用,却能大大减轻服务器的压力。
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前的一致,则可以直接返回前面的存储的运算结果。
function mult() {
var a = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
a *= arguments[i]
}
return a
}
console.log( mult( 2,3 ) ); // 6
console.log( mult( 2,3,4 ) ); // 24
加入缓存代理:
function mult() {
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( args in cache ) {
return cache[args]
}
return cache[args] = mult.apply( this,arguments )
}
} )()
console.log( ProxyMult( 1,2,3,4 ) ); // 24
console.log( ProxyMult( 1,2,3,4 ) ); // 24