代理模式这个设计模式对我来说有特殊的意义,因为这是我在工作中第一次分享学习的主题,当时我还是一个实习生,现在一眨眼已经过去很多年了。
设计模式是什么
当时分享的具体内容是什么我已经不记得了,而且其实我当时也并不太理解设计模式是什么,后来阅读了一些文章,我隐隐约约地理解设计模式就是代码的设计方案。
说到这,我想起之前看的《黑客与画家》,里面说优秀的程序员更像画家,这么一来不就对上了吗,两者关注的都是如何设计,是创造者,画家注意的是如何去设计构图,程序员注意的是如何去设计数据结构、代码结构、系统架构,一份设计良好的程序代码具有更好的可读性和可维护性,所以设计模式就是一些经过检验的、通用的、可复用的代码设计方案。
设计模式分类
wiki中将设计模式分为四类,分别是:
- 创建模式(creational patterns)
- 结构模式(structural patterns)
- 行为模式(behavioral patterns)
- 并发模式(concurrency patterns)
代理模式属于其中的结构模式。
代理
说到代理,我们可以将其对应到一个很生活化的词汇——中介,比如房产中介、留学中介等等一些中介机构,就是原本要直接建立关系的双方a和b之间多了一个中间人c,这个c就是代理;甚至我们可以就从字面上理解,代理作为动词是代为处理,作为名词就是代为处理的机构;代理可以为a处理事情,也可以为b处理事情。
当我们的代码中增加了具有代理功能的角色,就可以认为其应用了代理模式。比如ES6中新增的Proxy
。
// 假设存在一个对象a
let a = {
name: '鸡蛋'
}
// 但是我不希望代码里的其他部分对a直接进行访问
// 此时我们就可以创建一个a的代理,来处理其他内容对a的访问
let aProxy = new Proxy(a, {
get: function (a, key) {
return Reflect.get(a, key);
}
});
生活情境
那么什么情况下会使用到代理呢?以下罗列我想到的一些生活情境:
第一个,a想要与b建立关系,但是没有渠道、联系不上,刚好c可以接触到b,c就可以替a去联系b
第二个,a想要与b联系,但是又不想b知道自己,也可以使用代理c代替自己与b联系
第三个,a想要去b国留学,但是除了提交申请,对其他的流程不甚了解,就可以通过中介机构c提交申请并处理其他事宜
第四个,b作为一个重要资源,a不能随便访问,需要通过b的代理c来校验a的身份和权限,通过验证后c可以将资源b中符合a权限范围的内容展示给a
总结一下,代理在上述情境中起到的作用大致是:
- 建立渠道
- 保护信息
- 处理额外事项
解决的问题
现在我们来看wiki中描述的代理模式所解决的问题:
What problems can the Proxy design pattern solve?
- The access to an object should be controlled.
- Additional functionality should be provided when accessing an object.
When accessing sensitive objects, for example, it should be possible to check that clients have the needed access rights.
翻译过来大概是以下意思:
- 对受控对象的访问
- 访问对象时应提供附加的功能
怎么做
那么软件设计中的代理模式具体是怎么做的呢?wiki也给出了描述:
Define a separate
Proxy
object that
- can be used as substitute for another object (
Subject
) and- implements additional functionality to control the access to this subject.
This makes it possible to work through a Proxy object to perform additional functionality when accessing a subject. For example, to check the access rights of clients accessing a sensitive object.
To act as substitute for a subject, a proxy must implement the Subject interface. Clients can't tell whether they work with a subject or its proxy.
翻译过来大概是以下意思:
定义一个单独的代理对象
- 可用于替代另一个对象(主体)
- 并实现额外功能,以控制对该主体的访问。
这样就可以通过代理对象在访问主体时执行附加功能。例如,检查访问敏感对象的客户端的访问权限。
要替代主体,代理必须实现主体接口。客户无法分辨他们是在与主体还是其代理一起工作。
应用场景
既然设计模式是通用的解决方案,那必然有其应用场景。
1. wiki
以下是wiki给出的代理模式三个可能的应用场景
远程代理
在分布式对象通信中,本地对象代表远程对象(属于不同地址空间的对象)。本地对象是远程对象的代理,对本地对象的方法调用会导致对远程对象的远程方法调用。一个例子是自动取款机的实现,自动取款机可能持有远程服务器中银行信息的代理对象。
虚拟代理
在某些情况下,骨架表示可能比复杂或笨重的对象更有优势。当底层图像体积庞大时,可以使用虚拟代理对象来表示,并根据需要加载真实对象。
保护代理
保护代理可用于根据访问权限 控制对资源的访问。
2. 前端应用
那在前端有哪些场景可以应用代理模式呢?
虚拟代理
首先就是可以将虚拟代理应用在图片懒加载,这也是性能优化的一种手段。
在图片较大或者较多的情况下(列表)应用虚拟代理,具体操作就是,使用占位元素代替图片渲染,等待图片加载完毕或进入可视区域后,再进行真实图片的渲染。这里就是用占位元素作为真实图片的代理,以控制在图片未加载完成时如何去处理其渲染。
事件代理
比如利用事件冒泡,将事件监听器放置在更上级的元素,来实现事件代理。
我们知道DOM事件流有三个阶段:事件捕获 => 到达目标 => 事件冒泡,当我们想处理某个目标元素上的事件,可以在事件流到达目标元素时处理,也可以在事件流从目标元素冒泡到更上级的元素时进行处理。
使用事件代理,就相当于更上级的元素代替目标元素去处理事件,而不是目标元素直接去处理事件。
这在一些情况下,可以提高代码的性能。比如,要监听li上的点击事件:
<ul id="father">
<li><a href="#">链接1号</a></li>
<li><a href="#">链接2号</a></li>
<li><a href="#">链接3号</a></li>
<li><a href="#">链接4号</a></li>
<li><a href="#">链接5号</a></li>
<li><a href="#">链接6号</a></li>
</div>
假如我们给每个li都设置监听器,那至少要加6个监听器,如果标签进一步增多,那么性能开销会加大;而如果li支持动态添加,那就需要每增加一个li就得绑定一次事件。
此时使用事件代理就可以很大程度上提升代码的性能,只需要在ul上绑定一次事件就足矣;而且通常在列表中,点击每个列表项的事件处理逻辑往往差不多,只需在事件处理程序中使用id之类的属性对li进行区分即可。
再比如接口请求事件,前端如果使用axios请求接口,可以在axios的拦截器中先去校验本地是否存在token等认证信息,此时可以把这个axios看作一个代理,它代替我们去处理请求事件,在帮我们做了一系列校验以及格式处理等操作后,才会发起请求。
保护代理
也是对资源的访问控制,比如前端路由跳转,可以在路由守卫中做校验,是否具有目标路由的访问权限,如果有权限,才能进行跳转,此时可以把路由守卫看作一个代理。
以上可以看作是对目标对象(目标路由)的保护。
另外我们也可以使用ES6中的Proxy来代理真实的对象,以防止真实对象被意外访问或修改,当然也可以在通过代理对象来访问真实对象时,做一些额外的操作。
缓存代理
在一些场景下,我们可以把缓存也当作一种代理,比如vue中的计算属性,计算属性通常是根据普通属性计算而来,在普通属性没有更新的情况下也去计算,就有点浪费性能了,所以计算属性将最近一次的计算结果进行缓存,在没有更新的情况下,就可以将这个计算结果当做计算属性的一个代理。
总结
使用代理模式可以达到加强控制、提升性能、优化代码结构等效果。
之所以它在分类中属于结构模式,也很好理解,就是在a和b之间多了一个c,代码结构发生了变化,但a和b的行为都没有变,并且没有创建新的对象,c属于媒介而不是对象。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。