这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
代理模式是为一个对象提供 一个代用品或占位符,以便控制对它的访问。代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要时,提供一个替身对象来控制这个对象的访问,客户实际上访问的是替身对象。
虚拟代理实现图片预加载
在 Web
开发中,图片预加载是一种常用的技术,如果直接给某个 img
标签的 src
设置属性,由于图片过大或者网络不佳图片的位置往往有一段时间是空白的,常见的做法是先用一张 loading
占位图,使用异步方式加载图片,等图片加载好再填充到 img
节点里,这种场景很适合使用虚拟代理。
首先,创建一个普通本体对象,这个对象负责往页面中创建一个 img
标签,并提供一个对外的 setSrc
方法,外界调用,可以给该 img
设置 src
属性:
var myImage = (function () {
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
},
};
})();
myImage.setSrc(
"http://www.ghostw7.com/uploadslxy/allimg/160125/1T91352B-4.jpg"
);
复制代码
我们可以看到在网速较慢的时候,在图片被加载好之前,页面中有一段长长的空白时间。
引入代理对象 proxyImage
,通过这个代理对象,在图片被真正加载好之前,页面中将出现一张占位的 loading.gif
,提示用户图片正在加载。
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("loading.gif");
img.src = src;
},
};
})();
proxyImage.setSrc(
"http://www.ghostw7.com/uploadslxy/allimg/160125/1T91352B-4.jpg"
);
复制代码
目前,可以通过 proxyImage
间接访问 MyImage
,proxyImage
控制了客户对 MyImage
的访问,并且在此过程中加入一些额外的操作,比如在真正的图片加载好之前,先把 img
节点的 src
设置为一张 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 = "loading.gif";
img.src = src;
},
};
})();
MyImage.setSrc(
"http://www.ghostw7.com/uploadslxy/allimg/160125/1T91352B-4.jpg"
);
复制代码
单一职责原则:就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。
如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个,我们要降低耦合性。
如果图片体积很小或者未来网速很快的情况下,希望可以将预加载图片的这段代码从 MyImage
对象里删掉,这时候就不得不改动 MyImage
对象了。那这时,代理的作用就体现出来了。代理负责预加载图片,预加载的操作完成后,把请求重新交给本体 MyImage
。
代理和本体接口的一致性
上述说,如果有一天不需要预加载,那么就不需要代理对象,可以选择直接请求本体。其中关键是代理对象和本体都对外提供了 setSrc
方法,在用户看来,代理对象和本体是一致的。用户并不清楚代理和本体的区别,这样:
- 用户可以放心的请求代理,只需要关心是否能得到想要的结果。
- 在任何使用本体的地方都可以替换成使用代理。
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。
计算乘积
先创建一个用于求乘积的函数:
var muti = function () {
console.log("开始计算乘积");
var a = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
};
mult(2, 3); // 输出:6
mult(2, 3, 4); // 输出:24
复制代码
现在加入缓存代理函数:
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
复制代码
当第二次调用 proxyMult(1, 2, 3, 4)
时,本体 mult
函数并没有被计算,proxyMult
直接返回了之前缓存好的计算结果。
ajax 异步请求数据
项目中经常会遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,已经拉取的数据需要缓存在某个地方,下次请求同一页的时候,可以直接使用之前的数据。
这里也可以是用缓存代理。请求数据是异步操作,需要在回调中放入缓存。
用高阶函数动态创建代理
为加法、乘法、减法等创建缓存代理:
/**************** 计算乘积 *****************/
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
复制代码
其他代理模式
- 防火墙代理:控制网络资源访问,保护主题不让“坏人”接近。
- 远程代理:为一个对象在不同的地址空间提供局部代表。
- 保护代理:用于对象应该有不同访问权限的情况。
最后说一句
如果这篇文章对您有所帮助,或者有所启发的话,帮忙点赞关注一下,您的支持是我坚持写作最大的动力,多谢支持。