JavaScript版代理模式

本文是基于JavaScript的代理模式
内容包括:

  • 什么是代理模式
  • 两个经典的代理模式示例

代理模式

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

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

image-20211117083454819

但在 JavaScript 并不容易实现保护代理,因为我们无法判断谁访问了某个对象。

虚拟代理实现图片预加载

平时由于网络的不佳,导致图片出来前会有一片空白。所以我们限用一张 loading 图片占位,在异步方式加载图片

1.不使用代理模式

  • 违反了单一原则。MyImage除了要负责img节点的设置,还要负责预加载图片。这导致在处理其中一个职责时会因其强耦合性影响另一个职责。
  • 违反了开闭原则。倘若以后要去掉预加载,只能去更改MyImage对象,这不符合开闭原则。
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
// 创建一个本体对象
var MyImage = (function(){
// 创建标签
var imgNode = document.createElement( 'img' );
// 添加到页面
document.body.appendChild( imgNode );
// 创建一个新的img标签
var img = new Image;
// img 加载完成
img.onload = function(){
// 替换地址
imgNode.src = img.src;
};
return {
// 设置地址
setSrc: function( src ){
// 本地 loading 图片地址
imgNode.src = './loading.gif';
// src赋值给img后,img会进行加载,加载完成就会调用onload方法
img.src = src;
}
}
})();

myImage.setSrc( 'http://xxx.xx.com/xxx/xxx/xxx.jpg' ); //直接操作本体对象

imgNode是页面上真正的节点

img是用于等待加载图片

  • img.onload会等待img.src加载完毕后执行

上面代码流程:创建一个imgNode节点并加入body下,用./loading.gif占位,而img.src放入真正的url地址,此时会进行异步加载,等待加载完成后会调用img.onload方法,该方法将imgNode.src换成url地址的图片

2.使用代理模式

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
29
30
31
32
33
34
35
36
// 创建一个本体对象
var myImage = (function(){
// 创建标签
var imgNode = document.createElement( 'img' );
// 添加到页面
document.body.appendChild( imgNode );
return {
// 设置图片的src
setSrc: function( src ){
// 更改src
imgNode.src = src;
}
}
})();

// 创建代理对象
var proxyImage = (function(){
// 创建一个新的img标签
var img = new Image;
// img 加载完成事件
img.onload = function(){
// 调用 myImage 替换src方法
myImage.setSrc( this.src );
}
return {
// 代理设置地址
setSrc: function( src ){
// 预加载 loading
myImage.setSrc( './loading.gif' );
// 赋值正常图片地址
img.src = src;
}
}
})();

proxyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' ); //间接访问主体对象

代理缓存

1.对结果进行缓存

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
29
// 乘积函数
var mult = function(){
console.log( '开始计算乘积' );
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = 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 );
}
})();

proxyMult( 1, 2, 3, 4 ); // 输出:24
proxyMult( 1, 2, 3, 4 ); // 输出:24,缓存cache中的结果

2.动态代理

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
29
30
31
32
33
34
35
36
37
38
39
/**************** 计算乘积 *****************/
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
}
/**************** 计算加和 *****************/
var plus = function(){
var a = 0;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a + arguments[i];
}
return a;
}
/**************** 创建缓存代理的工厂 *****************/
var createProxyFactory = function( fn ){
// 缓存结果
var cache = {};
return function(){
// 将参数转换成字符串
var args = Array.prototype.join.call( arguments, ',' );
// 遍历缓存结果如果存在直接返回结果
if ( args in cache ){
return cache[ args ];
}
// 不存在进行相应的计算并保存结果
return cache[ args ] = fn.apply( this, arguments );
}
};

// 创建乘法和加法
var proxyMult = createProxyFactory( mult ),proxyPlus = createProxyFactory( plus )

alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10