动机由于一个对象不想或者不能直接引用另外一个对象,所以需要通过通过一个称之为代理的第三者来实现间接引用代理模式就是为目标对象创造一个代理对象,在客户端和目标对象之间起到中介的作用这样就可以在代理对象里增加一些逻辑判断、调用前或调用后执行一些操作,从而实现了扩展目标的功能并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务 通过引入一个新的对象(如小图片和远程代理对象)来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。定义 代理模式(ProxyPattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。生活中的案例: 火车票代购、房产中介、律师、海外代购、明星经纪人类图和时序图 代理模式包含如下角色:Subject:抽象主题角色Proxy:代理主题角色RealSubject:真实主题角色类图 时序图 一个例子明星经纪人 abstractclassStar{abstractanswerPhone():void;}classAngelababyextendsStar{publicavailable:booleantrue;answerPhone():void{console。log(你好,我是Angelababy。);}}classAngelababyAgentextendsStar{constructor(privateangelababy:Angelababy){super();}answerPhone():void{console。log(你好,我是Angelababy的经纪人。);if(this。angelababy。available){this。angelababy。answerPhone();}}}letangelababyAgentnewAngelababyAgent(newAngelababy());angelababyAgent。answerPhone();场景事件委托代理事件捕获指的是从document到触发事件的那个节点,即自上而下地去触发事件事件冒泡是自下而上地去触发事件绑定事件方法的第三个参数,就是控制事件触发顺序是否为事件捕获。true为事件捕获;false为事件冒泡,默认false。 bodyulidlistli1lili2lili3liulbody虚拟代理(图片预加载)app。jsletexpressrequire(express);letpathrequire(path)letappexpress();app。get(imagesloading。gif,function(req,res){res。sendFile(path。join(dirname,req。path));});app。get(images:name,function(req,res){setTimeout((){res。sendFile(path。join(dirname,req。path));},2000);});app。get(,function(req,res){res。sendFile(path。resolve(index。html));});app。listen(8080);index。html!DOCTYPEhtmlhtmllangenheadmetacharsetUTF8metanameviewportcontentwidthdevicewidth,initialscale1。0metahttpequivXUACompatiblecontentieedgetitleDocumenttitlestyle。bgcontainer{width:600px;height:400px;margin:100pxauto;}。bgcontainerbgimage{width:100;height:100;}styleheadbodybuttondatasrcimg02。bs178。combkghfc2d0dabef675ea7。jpg背景1buttonbuttondatasrcimg02。bs178。combkgh74d38974bcfa2664。jpg背景2buttonimgidbgimagesrca2020imgdataimg。jpgdatasrcimg02。bs178。combkghfc2d0dabef675ea7。jpgbodyhtml虚拟代理(图片懒加载)当前可视区域的高度window。innerHeightdocument。documentElement。clientHeight元素距离可视区域顶部的高度getBoundingClientRect()。topgetBoundingClientRectDOMRect对象包含了一组用于描述边框的只读属性left、top、right和bottom,单位为像素。除了width和height外的属性都是相对于视口的左上角位置而言的!DOCTYPEhtmlhtmllangenheadmetacharsetUTF8metanameviewportcontentwidthdevicewidth,initialscale1。0metahttpequivXUACompatiblecontentieedgetitleLazyLoadtitlestyle。image{width:300px;height:200px;backgroundcolor:CCC;}。imageimg{width:100;height:100;}styleheadbodyimgdatasrcimg02。bs178。combkghfc2d0dabef675ea7。jpgimgdatasrcimg02。bs178。combkgh74d38974bcfa2664。jpgimgdatasrcimg02。bs178。combkghfc2d0dabef675ea7。jpgimgdatasrcimg02。bs178。combkgh74d38974bcfa2664。jpgimgdatasrcimg02。bs178。combkghfc2d0dabef675ea7。jpgimgdatasrcimg02。bs178。combkgh74d38974bcfa2664。jpgimgdatasrcimg02。bs178。combkghfc2d0dabef675ea7。jpgimgdatasrcimg02。bs178。combkgh74d38974bcfa2664。jpgimgdatasrcimg02。bs178。combkghfc2d0dabef675ea7。jpgimgdatasrcimg02。bs178。combkgh74d38974bcfa2664。jpgbodyhtml缓存代理 有些时候可以用空间换时间正整数的阶乘(factorial) 一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1constfactorialfunctionf(num){if(num1){return1;}else{return(numf(num1));}}constproxyfunction(fn){constcache{};缓存对象returnfunction(num){if(numincache){returncache〔num〕;使用缓存代理}returncache〔num〕fn。call(this,num);}}constproxyFactorialproxy(factorial);console。log(proxyFactorial(5));console。log(proxyFactorial(5));console。log(proxyFactorial(5));斐波那契数列(Fibonaccisequence) 指的是这样一个数列:1、1、2、3、5、8、13、21、34。在数学上,斐波那契数列以如下被以递推的方法定义:F(1)1,F(2)1,F(n)F(n1)F(n2)(n3,nN)letcount0;functionfib(n){count;returnn2?1:fib(n1)fib(n2);}varresultfib(10);console。log(result,count);55110letcount0;constfibWithCache(function(){letcache{};functionfib(n){count;if(cache〔n〕){returncache〔n〕;}letresultn2?1:fib(n1)fib(n2);cache〔n〕result;returnresult;}returnfib;})();varresultfibWithCache(10);console。log(result,count);5517防抖代理通过防抖代理优化可以把多次请求合并为一次,提高性能节流与防抖都是为了减少频繁触发事件回调节流(Throttle)是在某段时间内不管触发了多少次回调都只认第一个,并在第一次结束后执行回调防抖(Debounce)就是在某段时间不管触发了多少回调都只看最后一个节流!DOCTYPEhtmlhtmllangenheadmetacharsetUTF8metanameviewportcontentwidthdevicewidth,initialscale1。0metahttpequivXUACompatiblecontentieedgetitleDocumenttitlestylecontainer{width:200px;height:400px;border:1pxsolidred;overflow:auto;}container。content{height:4000px;}styleheadbodybodyhtml防抖!DOCTYPEhtmlhtmllangenheadmetacharsetUTF8metanameviewportcontentwidthdevicewidth,initialscale1。0metahttpequivXUACompatiblecontentieedgetitleDocumenttitlestylecontainer{width:200px;height:400px;border:1pxsolidred;overflow:auto;}container。content{height:4000px;}styleheadbodybodyhtml防抖案例未防抖bodyulidtodosulbody app。jsletexpressrequire(express);letappexpress();app。use(express。static(dirname));lettodos〔{id:1,text:a,completed:false},{id:2,text:b,completed:false},{id:3,text:c,completed:false},〕;app。get(todos,function(req,res){res。json(todos);});app。get(toggle:id,function(req,res){letidreq。params。id;todostodos。map(item{if(item。idid){item。completed!item。completed;}returnitem;});res。json({code:0});});app。listen(8080);防抖案例防抖 todos。htmlbodyulidtodosul app。jsapp。get(toggle:ids,function(req,res){letidsreq。params。ids;idsids。split(,)。map(itemparseInt(item));todostodos。map(item{if(ids。includes(item。id)){item。completed!item。completed;}returnitem;});res。json({code:0});});代理跨域正向代理正向代理的对象是客户端,服务器端看不到真正的客户端通过公司代理服务器上网 反向代理反向代理的对象的服务端,客户端看不到真正的服务端nginx代理应用服务器 proxyserver。jsconsthttprequire(http);consthttpProxyrequire(httpproxy);创建一个代理服务constproxyhttpProxy。createProxyServer();创建http服务器并监听8888端口letserverhttp。createServer(function(req,res){将用户的请求转发到本地9999端口上proxy。web(req,res,{target:http:127。0。0。1:9999});监听代理服务错误proxy。on(error,function(err){console。log(err);});});server。listen(8888,0。0。0。0); realserver。jsconsthttprequire(http);letserverhttp。createServer(function(req,res){res。end(9999);});server。listen(9999,0。0。0。0);代理跨域nginx代理跨域webpackdevserver代理跨域客户端代理跨域当前的服务启动在origin(3000端口)上,但是调用的接口在target(4000端口)上postMessage方法可以安全地实现跨源通信otherWindow:其他窗口的一个引用message:将要发送到其他window的数据message将要发送到其他window的数据targetOrigin通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串(表示无限制)或者一个URIotherWindow。postMessage(message,targetOrigin,〔transfer〕);data从其他window中传递过来的对象origin调用postMessage时消息发送方窗口的originsource对发送消息的窗口对象的引用window。addEventListener(message,receiveMessage,false); origin。jsletexpressrequire(express);letappexpress();app。use(express。static(dirname));app。listen(3000); target。jsletexpressrequire(express);letappexpress();letbodyParserrequire(bodyparser);app。use(bodyParser。urlencoded({extended:true}));app。use(express。static(dirname));letusers〔〕;app。post(register,function(req,res){letbodyreq。body;lettargetbody。target;letcallbackbody。callback;letusernamebody。username;letpasswordbody。password;letuser{username,password};letidusers。length0?1:users〔users。length1〕。id1;user。idid;users。push(user);res。status(302);res。header(Location,{target}?callback{callback}args{id});res。end();});app。listen(4000); reg。html!DOCTYPEhtmlhtmllangenheadmetacharsetUTF8metanameviewportcontentwidthdevicewidth,initialscale1。0metahttpequivXUACompatiblecontentieedgetitleDocumenttitleheadbodybodyhtml target。html!DOCTYPEhtmlhtmllangenheadmetacharsetUTF8metanameviewportcontentwidthdevicewidth,initialscale1。0metahttpequivXUACompatiblecontentieedgetitleDocumenttitleheadbodybodyhtml。proxy接受一个函数,然后返回一个新函数,并且这个新函数始终保持了特定的上下文语境。jQuery。proxy(function,context)function为执行的函数,content为函数的上下文this值会被设置成这个object对象!DOCTYPEhtmlhtmllangenheadmetacharsetUTF8metanameviewportcontentwidthdevicewidth,initialscale1。0metahttpequivXUACompatiblecontentieedgetitlejqueryproxytitleheadbodybuttonidbtn点我变红buttonbodyhtmlfunctionproxy(fn,context){returnfunction(){returnfn。call(context,arguments);}}ProxyProxy用于修改某些操作的默认行为Proxy可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器ProxydefinePropertyletwang{name:wanglaoshi,age:29,height:165}letwangMamanewProxy(wang,{get(target,key){if(keyage){returnwang。age1;}elseif(keyheight){returnwang。height5;}returntarget〔key〕;},set(target,key,val){if(keyboyfriend){letboyfriendval;if(boyfriend。age40){thrownewError(太老);}elseif(boyfriend。salary20000){thrownewError(太穷);}else{target〔key〕val;returntrue;}}}});console。log(wangMama。age);console。log(wangMama。height);wangMama。boyfriend{age:41,salary:3000}Vue2和Vue3 Vue2中的变化侦测实现对Object及Array分别进行了不同的处理,Object使用了Object。definePropertyAPI,Array使用了拦截器对Array原型上的能够改变数据的方法进行拦截。虽然也实现了数据的变化侦测,但存在很多局限,比如对象新增属性无法被侦测,以及通过数组下边修改数组内容,也因此在Vue2中经常会使用到set这个方法对数据修改,以保证依赖更新。 Vue3中使用了es6的ProxyAPI对数据代理,没有像Vue2中对原数据进行修改,只是加了代理包装,因此首先性能上会有所改善。其次解决了Vue2中变化侦测的局限性,可以不使用set新增的对象属性及通过下标修改数组都能被侦测到。对比代理模式VS适配器模式 适配器提供不同接口,代理模式提供一模一样的接口代理模式VS装饰器模式 装饰器模式原来的功能不变还可以使用,代理模式改变原来的功能