介绍
代理是一个对象,它可以用来控制对本体对象的访问,它与本体对象实现了同样的接口,代理对象会把所有的调用方法传递给本体对象的;代理模式最基本的形式是对访问进行控制,而本体对象则负责执行所分派的那个对象的函数或者类,简单的来讲本地对象注重的去执行页面上的代码,代理则控制本地对象何时被实例化,何时被使用;
优点
1. 可以保护对象,代理对象可以代替本体被实例化,并使其可以被远程访问;
2. 优化性能,减少开销很大的对象
3. 缓存结果
实现
老规矩举个栗子来解释一下:小明喜欢上了隔壁班的小红,但是又不好意思去表白,所以就找舍友小李帮忙去送情书,那小李就是个代理
// 先声明小红对象
var girl = function (name) {
this.name = name;
};
// 这是小明
var ming= function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
console.log("Hi " + girl.name + ", 小明送给你:" + gift);
}
};
// 小李
var proxyLi = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
(new ming(girl)).sendGift(gift); // 给好基友打call
}
};
var proxy = new proxyLi(new girl("美丽的小红"));
proxy.sendGift("一封情书");
//Hi 美丽的小红, 小明送给你:一封情书
代理模式的分类
代理模式根据职责的不同划分了不同分类,这里主要说一下在js中常见的几种
1.ES6的proxy
// 明星
let star = {
name: 'CXK',
age: 25,
phone: '13910733521'
}
// 经纪人
let agent = new Proxy(star, {
get: function (target, key) {
if (key === 'phone') {
// 保护隐私返回经纪人的手机号
return '18611112222'
}
if (key === 'price') {
// 明星不报价,经纪人报价
return 5000000
}
return target[key]
},
set: function (target, key, val) {
if (key === 'customPrice') {
if (val < 4000000) {
// 最低 400w
throw new Error('这点钱不太够')
} else {
target[key] = val
return true
}
}
}
})
// 主办方
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)
// 想自己提供报价(砍价,或者高价争抢)
agent.customPrice = 8000000
//agent.customPrice = 2000000 // 报错:这点钱不太够
console.log('customPrice', agent.customPrice)
CXK
25
18611112222
5000000
customPrice 8000000
2.虚拟代理
2.1使用虚拟代理实现图片的预加载
在网页开发中,图片的预加载是一种比较常用的技术,如果直接给img标签节点设置src属性的话,如果图片比较大的话,或者网速相对比较慢的话,那么在图片未加载完之前,图片会有一段时间是空白的场景,这样对于用户体验来讲并不好,那么这个时候我们可以在图片未加载完之前我们可以使用一个loading加载图片来作为一个占位符,来提示用户该图片正在加载,等图片加载完后我们可以对该图片直接进行赋值即可;
// 方案一:不使用代理的预加载图片函数如下
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
var img = new Image();
img.onload = function(){
imgNode.src = this.src;
};
return {
setSrc: function(src) {
imgNode.src = "loading.gif";
img.src = src;
}
}
})();
// 调用方式
myImage.setSrc("pic.png");
//方案二:使用代理的预加载图片函数如下
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("pic.png");
具体分析一下两种写法的优劣
方案一:创建img标签--->插入img标签-->创建img对象-->onloading-->返回设置图片对象
缺点:代码耦合严重,当需求调整不需要预加载图片的时候,又得修改整个对象
方案二:myImage中创建img标签--->myImage中插入img标签-->myImage中返回设置imgNode的src方法-->ProxyImage中创建img对象-->ProxyImage中书写onload方法-->ProxyImage中返回设置图片的方法
优点:两个函数各自负责一件事情,当不需要预加载的时候,直接调用本体方法即可
2.2 使用虚拟代理合并http请求
比如在做后端系统中,有表格数据,每一条数据前面有复选框按钮,当点击复选框按钮时候,需要获取该id后需要传递给给服务器发送ajax请求,服务器端需要记录这条数据,去请求,如果我们每当点击一下向服务器发送一个http请求的话,对于服务器来说压力比较大,网络请求比较频繁,但是如果现在该系统的实时数据不是很高的话,我们可以通过一个代理函数收集一段时间内(比如说2-3秒)的所有id,一次性发ajax请求给服务器,相对来说网络请求降低了, 服务器压力减少了;
<body>
<div id="wrapper">
<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
</div>
</body>
<script type="text/javascript">
// 模拟http请求
var synchronousFile = function (id) {
console.log('开始同步文件,id 为: ' + id);
};
var inputs = document.getElementsByTagName('input')
var wrapper = document.getElementById('wrapper')
wrapper.onclick = function (e) {
if (e.target.tagName === 'INPUT' && e.target.checked) {
proxySynchronousFile(e.target.id)
}
}
var proxySynchronousFile = (function () {
var cacheIds = [], // 保存一段时间内需要同步的 ID
timeId = 0
return function (id) {
if (cacheIds.indexOf(id) < 0) {
cacheIds.push(id)
}
clearTimeout(timeId)
timeId = setTimeout(() => { // 2 秒后向本体发送需要同步的 ID 集合
synchronousFile(cacheIds.join(','))
cacheIds = [] // 清空ID集合
}, 2000)
}
})()
</script>
3.缓存代理
缓存代理的含义就是对第一次运行时候进行缓存,当再一次运行相同的时候,直接从缓存里面取,这样做的好处是避免重复一次运算功能,如果运算非常复杂的话,对性能很耗费,那么使用缓存对象可以提高性能
// 计算乘法
var mult = function(){
var a = 1;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a = a*arguments[i];
}
return a;
};
// 计算加法
var plus = function(){
var a = 0;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a += arguments[i];
}
return a;
}
// 代理函数
var proxyFunc = 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 = proxyFunc(mult);
console.log(proxyMult(1,2,3,4)); // 24
console.log(proxyMult(1,2,3,4)); // 缓存取 24
var proxyPlus = proxyFunc(plus);
console.log(proxyPlus(1,2,3,4)); // 10
console.log(proxyPlus(1,2,3,4)); // 缓存取 10