Javascript-设计模式_代理模式
前言
代理模式是一种对程序对象进行控制性访问的一类解决方案
引入代理模式,其实是为了实现单一职责的面向对象设计原则
单一职责其实就是指在一个类中(js中通常指对象和函数等),应仅有一个引起它变化的原因。这样会帮助程序设计具有良好的健壮和高内聚特性,从而当变化发生时,程序设计会尽量少的受到意外破坏
代理模式有多种方法,保护代理、虚拟代理、缓存代理等
但在javascript中,代理模式最常用到的两种方法是虚拟代理和缓存代理
代理模式
定义
所谓的的代理模式就是为一个对象找一个替代对象,以便对原对象进行访问
代理模式的关建是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际访问的是替身对象,替身对象对请求做出一些处理后,再把请求转交给本体对象
使用代理的原因是我们不愿意或者不想对原对象进行直接操作,我们使用代理就是让它帮原对象进行一系列的操作,等这些东西做完后告诉原对象就行了,就像我们生活的那些明星的助理经纪人一样
很多明星都是有经纪人的,如果要联系明显进行商演或者开演唱会之类的商业活动通过是需要先跟经纪人取得联系的,跟经纪人谈好了合作事宜之后经纪人再转达给某明星,然后某明星才会去参加活动;同样租房也是一个同样的道理,我们不管是租房还是买房,第一反应肯定是找链家这类的平台,因为我们只需要跟链家进行沟通,而链家去跟房东沟通,省去了我们直接和房东沟通的步骤;因为链家就是一个代理模式,它代理了这个房东的房源
应用场合
图片预加载、合并HTTP请求、惰性加载、缓存代理等
保护代理
保护代理主要实现了访问主体的限制行为,代理B可以帮助A过滤掉一些请求
// 主体,发送消息 function sendMsg(msg) { console.log(msg) } // 代理,对消息进行过滤 function proxySendMsg(msg) { // 无消息则直接返回 if (typeof msg === 'undefined') { console.log('deny') return } // 有消息则进行过滤 msg = ('' + msg).replace(/泥\s*煤/g, '') sendMsg(msg) } sendMsg('泥煤呀泥 煤呀') // 泥煤呀泥 煤呀 proxySendMsg('泥煤呀泥 煤') // 呀 proxySendMsg() // deny
它的意图很明显,在访问主体之前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体,这数据保护代理的形式
有消息的时候对敏感字符进行了处理,这属于虚拟代理的模式
虚拟代理
在理解虚拟代理时,可以将其想象为一个经纪人,客户程序需要通过这个虚拟代理(经纪人)来调用本体对象的方法
式例一 图片loading预加载
// 通过虚拟代理实现图片预加载 // 代理模式进行图片预加载的实现思路是: 通过代理对象获取实际显示图片地址并进行加载,同时先让本体对象显示预加载图片,待代理对象将实际图片地址加载完毕后传递给本体对象进行显示即可 // 本体对象 const myImage = (function(){ var imgNode = new Image() document.body.appendChild(imgNode) return { setSrc: function(src){ imgNode.src = src } } })() // 代理对象 const proxyImage = (function(){ var img = new Image() // 1、代理对象新建一个img对象 img.onload = function(){ // 4、代理对象img加载真实图片src完成后将src传递给本体对象显示 myImage.setSrc(this.src) } return { setProxySrc: function(src){ myImage.setSrc('../images/loding.gif') // 2、代理对象控制本体对象使用加载图片src img.src = src // 3、代理对象的img对象获取将要传递给本体对象的真实图片src } } })() // 通过代理对象来对本体对象进行访问 proxyImage.setProxySrc('https://p1.ssl.qhimgs1.com/t0153297036f4471d81.jpg')
式例二 合并HTTP请求,减少网络请求资源消耗
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>代理模式-虚拟代理合并HTTP请求</title> </head> <body> <div> <input type="checkbox" id="1" />1 <input type="checkbox" id="2" />2 <input type="checkbox" id="3" />3 <input type="checkbox" id="4" />4 <input type="checkbox" id="5" />5 <input type="checkbox" id="6" />6 <input type="checkbox" id="7" />7 <input type="checkbox" id="8" />8 <input type="checkbox" id="9" />9 </div> </body> <script> const synchronousFile = function(id){ console.log('开始同步:' + id); } const proxySynchronousFile = (function(){ var cache = [], // 保存一段时间内需要同步的id timer; // 定时器 // 闭包函数 return { proxySyncFile: function(id){ cache.push(id) if(timer){ // 保证不会覆盖已经启动的定时器 return } timer = setTimeout(function(){ synchronousFile(cache.join(',')) // 2秒后向本体对象发送需要同步的ID集合 clearTimeout(timer) // 清空定时器 timer = null cache.length = 0 // 清空id集合 }, 2000) } } })() const check = document.getElementsByTagName('input') for(let i=0; i<check.length; i++){ check[i].onclick = function(){ proxySynchronousFile.proxySyncFile(this.id) } } </script> </html>
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时存储,在下次运算时,如果传递进来的参数和之前的一致,则可以直接返回前面存储的结果
式例一 计算乘积
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>代理模式-缓存代理</title> </head> <body> <input type="text" id="input1">* <input type="text" id="input2"> <div id="result"></div> <button type="button" id="btn">计算</button> </body> <script> // 缓存代理示例: 计算乘积 // 本体对象 var calculate = function(){ var a = 1; for(var i=0; i<arguments.length; i++){ a = a*arguments[i] } return a; } // 代理对象,创建缓存代理的工厂,参数fn可以为任意需要进行代理的函数,除了上述计算乘积的本体对象函数外,还可以是计算加减或进行其他操作的本体函数 const proxyCalculate = function(fn){ const resultCache = {}; return function(){ const args = Array.prototype.join.call(arguments, ',') if(args in resultCache){ //测试对象中是否有对应的name,有则直接返回该name的值 return resultCache[args] } return resultCache[args] = fn.apply(this, arguments) } } document.getElementById('btn').onclick = function(){ const v1 = document.getElementById('input1').value const v2 = document.getElementById('input2').value const result = proxyCalculate(calculate)(v1, v2) document.getElementById('result').innerHTML = result } // 总结: 代理模式还有多种,比如保护代理、远程代理等,但js中常用的代理模式有虚拟代理和缓存代理两种。 </script> </html>
模式特点
-
代理对象可预先处理请求,再决定是否转交给本体
-
代理和本体对外显示接口保持一致性
-
代理对象仅对本体做一次包装
优缺点
优点
-
可拦截和监听外部对本体对象的访问
-
复杂运算前可以进行校验或资源管理
-
对象职能粒度细分,函数功能复杂度降低,符合 “单一职责原则”
-
依托代理,可额外添加扩展功能,而不修改本体对象,符合 “开发-封闭原则”
缺点
-
额外代理对象的创建,增加部分内存开销
-
处理请求速度可能有差别,非直接访问存在开销,但 “虚拟代理” 及 “缓存代理” 均能提升性能
模式细分
代理模式的变体种类非常多,限于篇幅及其在JavaScript中的适用性,这里只简约介绍一下这些代理,就不一一详细展开说明
- 虚拟代理(将开销大的运算延迟到需要时执行)
- 缓存代理(为开销大的运算结果提供缓存)
- 保护代理(黑白双簧,代理充当黑脸,拦截非分要求)
- 防火墙代理(控制网络资源的访问)
- 远程代理(为一个对象在不同的地址控件提供局部代表)
- 智能引用代理(访问对象执行一些附加操作)
- 写时复制代理(延迟对象复制过程,对象需要真正修改时才进行)