[Toc] >[success] # 保护代理 ~~~ 1.保护代理:'你需要满足代理的规定的某些条件才能访问本体',简单的说本体喜欢玫瑰 你给你的好用康乃馨,他是不会将康乃馨给女神,直到你给了玫瑰给你朋友他才会帮你 转交给女神 ~~~ >[success] # 虚拟代理 ~~~ 1.虚拟代理:'把一些开销很大的对象,延迟到真正需要他的时候才创建' ~~~ >[danger] ##### 没有使用虚拟代理来实现一个页面生成图片 ~~~ 1.根据书中的描述下面写法首先是没错了,但是出于几点考虑,第一个点如果网页加载速度 过慢,那么在生成图片的时候会出现一段空白期 ~~~ ~~~ var myImage = (function () { // 创建img 节点 var imgNode = document.createElement('img') document.body.appendChild(imgNode) return { setSrc:function (src) { imgNode.src = src } } })() myImage.setSrc('图片地址') ~~~ >[danger] ##### 虚拟代理实现图片预加载 ~~~ 1.通过虚拟代理的概念来解决这个问题,虚拟代理说'把一些开销很大的对象,延迟到真正需要他的时候才创建' 针对这个概念带入'开销很大的是图片加载',因此需要一个代理对象来决定在什么时候来加载这个图片加载 2.分析代码: 1.我们只是决定什么时候启用myImage 2.我们不要去干涉myImage 做的事情,这个事情就是创建图片节点 3.要做的: 1.要做的决定什么时候加载启动myImage 方法 2.我们要保证和本体有一样的入口方法。 ~~~ [imgonload 説明]( https://blog.csdn.net/github_37421511/article/details/84069867 ) ~~~ var myImage = (function () { // 创建img 节点 var imgNode = document.createElement('img') document.body.appendChild(imgNode) return { setSrc:function (src) { imgNode.src = src } } })() // 我是代理决定什么时候加载本体 var proxyImage = (function () { /** * 1.我们只是决定什么时候启用myImage * 2.我们不要去干涉myImage 做的事情,这个事情就是创建图片节点 * 要做的: * 1.要做的决定什么时候加载启动myImage 方法 * 2.我们要保证和本体有一样的入口这样有一天不需要我们代理了调用本体还是好用的 * **/ var img = new Image; // 图片加载完成时,触发onload事件 img.onload = function () { // 调用本体 myImage.setSrc(this.src) } return { setSrc:function (src) { // 先用本地图片作为加载,本地图片生成速度肯定快于网站页面请求 myImage.setSrc('本地图片') img.src = src } } })() ~~~ >[danger] ##### 代理是多次一举?还是有必要? ~~~ 1.上面代码我们不用代理也能写,写法就像下面的效果 2.下面的代码也完成我们想要的图片如果没有加载完之前,出现一个站位图片, 为什么还需要代理一次产生额外的代码呢? 2.1 书中举出了一个例子,如果N年后网速变动特别快我们时候还需要这个图片站位的方式 不需要的话我们想删除这个站位功能可能需要对这个'MyImage',进行重写,这类的更改就违反了 '开放封闭原则' 2.2 之前有提到一个原则'开放封闭原则',现在有一个新的原则需要引入'单一职责',引用书中的描述: 就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责, 就意味着这个对象将变得巨大,引起它变化的原因会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中, 如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计,带变化发生 时,设计可能会遭到意外的破坏。 2.3.这样的好处就像书中说的: 纵观整个程序,我们并没有改变或者'增加 MyImage 的接口',但是通过'代理对象', 实际上给系统'添加了新的行为'。这是符合'开放—封闭原则'。给 img 节点设置 src 和'图片预加载这两个功能', 被隔离在'两个对象里',它们可以各自变化而'不影响对方'。何况就算有一天我们不再需要预加载, 那么只需要'改成请求本体'而不是'请求代理对象'即可。 ~~~ ~~~ 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.baidu.com/img/bd_logo1.png'; // 当没有加载完成使用的图片 img.src = src; } } })(); MyImage.setSrc('https://avatars3.githubusercontent.com/u/15172026?v=4&s=460'); ~~~ >[danger] ##### 虚拟代理的案例(二)-- 节流 ~~~ 1.书中还举了个例子,在web开发中最能引起共鸣的开销就是网络请求,书中根据这个前提 举了一个例子我们有一个文件列表,当我们选中文件列表中的'checkbox'就会上传一个文件, 在手速足够快,足够频繁那么请求将会足够频繁 代码如下 ~~~ * html ~~~ <body> <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 </body> ~~~ * js ~~~ var synchronousFile = function(id) { console.log('发送后台数据' + id); }; var checkbox = document.getElementsByTagName('input'); for(var i = 0, c; c = checkbox[i++];) { c.onclick = function() { // 这个this指向当前dom节点 if(this.checked === true) { synchronousFile(this.id); } } } ~~~ * 进行改造将数据缓存起来,2秒后讲这些缓存的数据统一进行请求,这样解决频繁操作问题 ~~~ var synchronousFile = function(id) { console.log('发送后台数据' + id); }; var proxySynchronousFile = (function() { var cache = [], // 保存一段时间内需要同步的Id timer; // 定时器 return 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); } })(); var checkbox = document.getElementsByTagName('input'); for(var i = 0, c; c = checkbox[i++];) { c.onclick = function() { if(this.checked === true) { proxySynchronousFile(this.id); } } } ~~~ >[success] # 缓存代理 ~~~ 1.缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的 参数跟之前一致,则可以直接返回前面存储的运算结果 ~~~ >[danger] ##### 之前的缓存例子 ~~~ 1.下面的代码用es5 实现的一个缓存乘机的代码,更多优化或者es6的写法看历史章节里有讲过 ~~~ ~~~ var mult = (function () { var cache = {} return function () { var args = [].slice.apply(arguments) var key = args.toString() if(cache[key]){ console.log('121') return cache[key] } var count = 1 args.forEach(function (item) { count = count*item }) return cache[key] = count } })() console.log(mult(1,2,3)) console.log(mult(1,2,3)) 打印结果: 6 121 6 ~~~ >[danger] ##### 缓存代理改进上面的案例 ~~~ 1.现在这段代码有一个不太舒服的地方,它既做了缓存,又做了乘法计算,不太符合单一原则, 我们通过代码进行改进 ~~~ ~~~ // 目标对象 var mult = function() { console.log('开始计算乘积'); var a = 1; for (var i = 0; i< arguments.length; 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 ~~~ >[danger] ##### 缓存代理用于ajax异步请求数据 ~~~ 1.内容摘抄自设计模式和开发实践的书: 我们在常常在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次, 这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直 接使用之前的数据。 显然这里也可以引入缓存代理,实现方式跟计算乘积的例子差不多,唯一不同的是, 请求数据是个异步的操作,我们无法直接把计算结果放到代理对象的缓存中,而是要 通过回调的方式。具体代码不再赘述,读者可以自行实现。 ~~~ >[success] # 用高阶函数动态创建代理 ~~~ 1.通过传入高阶函数这种更加灵活的方式,可以为各种计算方法创建缓存代理。 现在这些计算方法被当作参数传入一个专门用于创建缓存代理的工厂中, 这样一来, 我们就可以为乘法、加法、减法等创建缓存代理, ~~~ >[danger] ##### 案例 ~~~ // 计算乘积 var mult = function() { var a = 1; for (var i = 0; i< arguments.length; 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 ~~~ >[success] # 书中的其他代理 ~~~ 1.防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。 2.远程代理:为一个对象在不同的地址空间提供局部代表,在 Java 中,远程代理可以是另一个虚拟机中的对象。 3.保护代理:用于对象应该有不同访问权限的情况。 4.智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数。 5.写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它6.进行复制操作。写时复制代理是虚拟代理的一种变体,DLL(操作系统中的动态链接库)是其典型运用场景。 ~~~