代理moshi

为一个对象提供一个代理,以便控制对它的访问。

关键:提供一个替身对象来控制对该对象的访问,以满足需求。

保护代理和虚拟代理

  • 保护代理:代理者替被代理者过滤掉某些请求,用于控制不同权限对目标对象的访问,在JavaScript中并不容易实现
  • 虚拟代理: 把一些开销很大的对象,延迟到真正需要它的时候才去创建

虚拟代理实现图片预加载

Image对象提供setSrc接口

1
2
3
4
5
6
7
8
9
10
var image = (function (){
var img = document.createElement(‘img’)
document.appendChild(img)
return {
setSrc: function (url){
img.src = url
}
}
})()

直接访问对image进行设置的时候,会有一定的加载时间。因此我们使用ProxyImage对象来对其进行占位设置,当图片加载成功的时候,才进行setSrc操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
var proxyImage = (function(){
var img = new Image()
img.onload = function(){
image.setSrc(this.src)
}
return {
setSrc: function (url){
image.setSrc(loading.gif)
img.src = url
}
}
})()

代理模式意义

  • 保证每个对象或者函数的纯净,满足“单一职责原则”,图片进进行src设置,代理对象实现图片预加载功能
  • 并不改变原对象的功能,但是为原对象满足了更多的功能,符合开放封闭原则。

接口一致性

代理和本体需保持接口的一致

  • 用户可放心的请求代理
  • 任何使用本体的地方都可替换成使用代理

假设代理或者本体都为一个方法,可以默认为一致的接口

应用,合并请求

通过代理模式,进行请求的合并,减少服务器压力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function request = function (id){
console.log(‘开始请求:’ + id)
}
function ProxyRequest = (function () {
var cache = new Set()
var timer
return function (id) {
cache.add(id)
if (timer){ return }
timer = setTimeout(function(){
request(cache.join(‘,’))
cache.clear()
timer = null
})
}
})()

惰性加载

使用代理模式代理一个“假的接口”,当在想要真正的触发时,才去加载并执行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var miniConsole = (function(){
var cache = [];
var handler = function( ev ){
if ( ev.keyCode === 113 ){
var script = document.createElement( 'script' );
script.onload = function(){
for ( var i = 0, fn; fn = cache[ i++ ]; ){ fn();}
};
script.src = 'miniConsole.js';
document.getElementsByTagName( 'head' )[0].appendChild( script ); document.body.removeEventListener( 'keydown', handler );
}
};
document.body.addEventListener( 'keydown', handler, false );
return {
log: function(){
var args = arguments;
cache.push( function(){
return miniConsole.log.apply( miniConsole, args ); });
} }
}
)();
miniConsole.log( 11 );
// miniConsole.js 代码
miniConsole = {
log: function(){
// 真正代码略
console.log( Array.prototype.join.call( arguments ) ); }
};

缓存代理

缓存复杂计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var cal = function(…args) {
return args.reduce((all, num) => all * num)
}
var proxyCal = (function (){
var cache = {}
return function(…args){
var key = args.join(‘,’)
if (cache[key]){
return cache[key]
}
return cache[key] = cal(…args)
}
})()

cal函数仅作相乘运算,缓存功能用proxyCal代理实现

同时,我们可以更加通用化,通过工厂模式创建代理

1
2
3
4
5
6
7
8
9
10
11
12
var proxyCacheFatory = function (fn){
var cache = {}
return function(…args){
var key = args.join(‘,’)
if (cache[key]){
return cache[key]
}
return cache[key] = fn(…args)
}
}

其他

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

其他语言实现

  • RealSubject类,真实实体
  • Proxy类,保存一个实体的引用,可以访问实体,并提供与实体相同的接口
  • Subject类,定义了两个类的共用接口,以保证访问实体的地方同样也可以访问代理。

总结

代理模式其实就是为了为对象提供一个代理类,来进行更多的操作或行为,以保护原对象或者满足更多的需求,保持原对象的“纯净”,在javascript中其实很常见,我们不必刻意的考虑使用代理模式的必要性,只有当对象无法满足要求时,为其添加代理即可。