1.定义
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
例子: 小明追女神的故事:小明想送花给女神A,内向的小明让同学B代替自己送花 同学B就是一个代理
2.保护代理和虚拟代理
保护代理: 保护代理用于控制不同权限的对象对目标对象的访问,但在 JavaScript 并不容易实现保护代理,因为我们无法判断谁访问了某个对象。
虚拟代理 虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建,例如同学B在女神A开心时才去帮小明买花给女神A
3.虚拟代理实现图片预加载
业务背景:图片预加载,图片太大先设置一个loading占位图,等图片加载完在替换 不适用虚拟代理写法
var myImage = (function () {
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
var img = new Image();
img.onload = function () {
imgNode.src = img.src;
};
return {
setSrc: function (src) {
imgNode.src = "https://www.zuiaiwanqian.com/images/test.png";
img.src = src;
},
};
})();
myImage.setSrc("https://www.zuiaiwanqian.com/images/preview-photo.gif")
可以实现图片预加载功能,但是该方法不满足面向对像设计的原则中的单一职责原则
单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。
如果一个对象承担了多项职责,就意味着这个类将变得巨大,引起它变化的原因可能会有很多个,面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏
解析
在上面这个方法中,除了负责给img设置图片,还负责了预加载,如果之后要把预加载功能去掉,就不得不改动myImage对象,这样子又违反了开放-封闭原则
所以可以把预加载放在代理中实现,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体
虚拟代理写法
// 本体
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("https://www.zuiaiwanqian.com/images/test.png");
img.src = src;
},
};
})();
proxyImage.setSrc("https://www.zuiaiwanqian.com/images/preview-photo.gif");
纵观整个程序,我们并没有改变或者增加myImage 的接口,但是通过代理对象,实际上给系统添加了新的行为。这是符合开放—封闭原则的。给img 节点设置src 和图片预加载这两个功能,被隔离在两个对象里,它们可以各自变化而不影响对方。何况就算有一天我们不再需要预加载,那么只需要改成请求本体而不是请求代理对象即可
4.代理和本地接口的一致性
上一个示例中,代理对象和本地都对外提供了setSrc方法,在用户看来,代理对象和本体是一致的, 代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别,这样子有两个好处
- 用户可以放心地请求代理,他只关心是否能得到想要的结果。
- 任何使用本体的地方都可以替换成使用代理。
5.虚拟代理合并HTTP请求
业务场景:点击checkbox发送请求给后端,可以把请求合并在一起,2s后一起发送 未使用虚拟代理
var synchronousFile = function (id) {
console.log("开始同步文件,id为" + id);
};
var checkbox = document.querySelectorAll("input");
for (var i = 0; i < checkbox.length; i++) {
checkbox[i].onclick = function () {
if (this.checked === true) {
synchronousFile(this.id);
}
};
}
以上代码如果用户点击checkbox速度很快1s可以点5个,这样子就会连续发送5个HTTP请求,造成服务器压力,可以使用虚拟代理将请求合并在一起2s后一块发送 使用虚拟代理
// 本体
var synchronousFile = function (id) {
console.log("开始同步文件,id为" + id);
};
// 虚拟代理对象
var proxySynchronousFile = (function () {
var cache = [];
var timer = null;
return function (id) {
cache.push(id);
if (timer) {
return;
}
timer = setTimeout(() => {
synchronousFile(cache.join(","));
clearTimeout(timer);
timer = null;
cache.length = 0; // 清空缓存
}, 2000);
};
})();
var checkbox = document.querySelectorAll("input");
for (var i = 0; i < checkbox.length; i++) {
checkbox[i].onclick = function () {
if (this.checked === true) {
synchronousFile(this.id);
}
};
}
6.虚拟代码在惰性加载的应用
如果需要加载一个js文件,但是在没有加载之前可以调用这个js文件中的API,这个时候就可以通过一个这个js的代理对象去缓存调用的API,等需要加载这个js时,再去这个缓存里面拿到这些方法依次调用
7.缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果 例子:计算乘积
- 计算乘积的函数
- 使用缓存代理,将出现过的值缓存下来
// 本体
var mult = function () {
console.log("开始计算乘积");
var a = 1;
for (let i = 0; i < arguments.length; i++) {
a = a * arguments[i];
}
return a;
};
// 缓存代理对象
var proxyMult = (function () {
var cache = {};
return function () {
var args = Array.prototype.join.call(arguments, ",");
console.log(cache);
if (args in cache) {
return cache[args];
}
return (cache[args] = mult.apply(this, arguments));
};
})();
console.log(proxyMult(1, 2, 3));
console.log(proxyMult(1, 2, 3));
8.用高阶函数动态创建代理
通过传入高阶函数这种更加灵活的方式,可以为各种计算方法创建缓存代理。创建一个缓存代理工厂,可以往里面加入不同的函数,如加法,减法
/**************** 计算乘积 *****************/
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
总结
代理模式包括许多小分类,在 JavaScript 开发中最常用的是虚拟代理和缓存代理。虽然代理模式非常有用,但我们在编写业务代码的时候,往往不需要去预先猜测是否需要使用代理模式。当真正发现不方便直接访问某个对象的时候,再编写代理也不迟。