时间:2025.04.21 分享人:赵文琦
1 理论体系背景
1.1 问题域
某些场景下,我们在直接访问对象时可能遇到问题,例如,当需要访问的对象位于远程机器上时,或者某些对象的创建开销很大,又或者某些操作需要安全控制,直接访问这些对象可能会给使用者或系统结构带来诸多不便。代理模式能够处理远程访问、创建开销高、访问安全控制等问题,实现灵活的系统结构。
在生活中,代理模式的场景是十分常见的,例如我们现在如果有租房、买房的需求,更多的是去找房屋中介机构,而不是直接寻找想卖房或出租房的人谈。此时,中介起到的作用就是代理的作用。中介和他所代理的客户在租房、售房上提供的方法可能都是一致的(收钱,签合同),可是中介作为代理却提供了访问限制,让我们不能直接访问被代理的客户。
1.2 术语体系
1.2.1什么是代理模式
代理模式是一种结构型设计模式,它通过引入一个代理对象来控制对另一个对象的访问或操作。代理对象和真实对象具有相同的接口,客户端代码通常无需关注代理对象和真实对象之间的差异。
代理模式的结构通常包含三个角色:
- Subject(抽象主题): 定义了真实对象和代理对象的共同接口,可以是接口或抽象类。
- RealSubject(真实主题): 真实对象是代理模式中要被访问或操作的对象,它具有具体的业务逻辑和功能。代理对象和真实对象通常都实现同一个接口。
- Proxy(代理): 代理对象是客户端和真实对象之间的中介,它包含了对真实对象的引用,并在其自身的接口中实现了与真实对象相同的方法。代理对象可以控制和管理对真实对象的访问,使用者通过代理对象来执行特定的操作。

代理模式的核心思想是通过代理对象间接地访问真实对象,从而在访问过程中添加额外的功能、控制访问行为、提供缓存等增强功能。代理模式可以在不对真实对象做修改的情况下,对其进行扩展,同时还能保持客户端代码与真实对象的解耦。
1 | // 租房接口(使用对象模拟接口) |
1.2.2代理模式的作用
代理模式的作用主要包括以下几个方面:
- 控制访问:代理模式可以控制对真实对象的访问,通过在代理对象中添加额外的控制逻辑,可以限制或过滤对真实对象的访问,从而提供更加安全可靠的访问方式。比如,可以在代理对象中检查权限,只有具备足够权限的用户才能访问真实对象。
- 延迟加载:代理模式可以实现延迟加载的效果,即只有在真正需要使用真实对象时才进行实例化。这样可以提升系统的性能和效率,避免不必要的资源消耗。比如,可以使用代理对象加载大型资源文件,只有在用户需要使用该资源时才真正加载。
- 缓存数据:代理模式可以用于缓存数据,通过在代理对象中维护一个缓存变量,可以将计算结果缓存起来,避免重复计算,提升程序的执行效率。比如,可以使用代理对象缓存网络请求的结果,避免重复发送相同请求。
- 增加额外功能:代理模式可以在真实对象的操作前后添加额外的逻辑,从而增加了额外的功能。如,可以在代理对象中插入日志记录、性能计算、异常处理等功能而不需要修改真实对象的代码。
- 对复杂性管理:代理模式可以将原本复杂的对象分解为多个简单的对象,分别由代理对象和真实对象来管理。这样可以降低系统的复杂性,提供更好的可维护性和可拓展性。
1.3 构件模式
1.4 所属的计算机学科核心问题模型
代理模式属于计算机学科中软件设计模式的核心问题模型。代理模式通过引入中间层,在保持系统灵活性的同时,实现了对复杂问题的优雅解决,是软件设计中处理间接访问和增强功能的经典方案。具体涉及以下核心问题领域:
1.4.1结构型设计模式
代理模式是典型的结构型设计模式,主要用于:
- 解耦组件:在客户端和目标对象之间引入中介层
- 控制访问:提供对目标对象的间接访问控制
- 增强功能:在不修改原始对象的前提下扩展功能
1.4.2核心解决的问题模型
代理模式主要针对以下软件开发中的核心问题:
| 问题类型 | 具体场景 |
|---|---|
| 访问控制 | 需要验证权限才能访问目标对象 |
| 资源延迟加载 | 需要按需加载大型资源(如图片) |
| 操作拦截 | 需要记录日志或审计目标对象操作 |
| 接口隔离 | 需要简化或限制目标对象的复杂接口 |
1.4.3设计原则体现
在面向对象的编程中,代理模式的合理使用能够很好的体现下面重要设计原则:
单一职责原则: 面向对象设计中鼓励将不同的职责分布到细粒度的对象中,代理模式在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念。
开放-封闭原则:代理可以随时从程序中去掉,而不用对其他部分的代码进行修改,在实际场景中,随着版本的迭代可能会有多种原因不再需要代理,那么就可以容易的将代理对象换成原对象的调用
依赖倒转原则: 这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。客户端依赖抽象(接口)而非具体实现
2 其他相关技术体系
2.1 ES6中的代理(Proxy)
在JavaScript中,代理模式可以很容易地通过ES6引入的Proxy对象实现。Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 是 ES6 中引入的一个强大特性,它允许我们以一种简洁且易于理解的方式来控制外部对对象的访问。它的功能与设计模式中的代理模式非常相似。使用 Proxy 的好处在于,对象可以专注于其核心逻辑,而将一些非核心的逻辑(如属性访问前的日志记录、属性设置前的验证等)交给 proxy 来处理。这样,我们可以实现关注点分离,降低对象的复杂性。
Proxy 对象用于创建一个对象的代理,从而实现对基本操作的拦截和自定义(例如属性查找、赋值、枚举、函数调用等)。可以理解为 Proxy 在对象之前设置了一个“拦截”,当监听的对象被访问时,都必须经过这层拦截。我们可以在这个拦截中对原对象进行处理,返回需要的数据格式。
使用方法:
1 | let obj = new Proxy(target, handler); |
参数:target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至另一个代理)。handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 target 的指定行为。handler 对象是一个容纳一批特定属性的占位符对象,它包含有 Proxy 的各个捕获器(trap),提供属性访问的方法。所有的捕捉器是可选的,如果没有定义某个捕捉器,那么就会保留源对象的默认行为。
1 | let target = { count: 0 }; |
Proxy 支持的拦截操作一共 13 种:
get()
get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
set()
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
apply()
apply方法拦截函数的调用、call和apply操作。
apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
1 | let twice = { |
has()
has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
has()方法可以接受两个参数,分别是目标对象、需查询的属性名。
1 | let handler = { |
3 应用场景
在前端开发中,代理模式应用广泛,以下是几个常见的使用场景:
- Vue3 的响应式系统:Vue3 使用
Proxy实现数据响应式。 - 缓存优化:通过代理实现接口数据缓存。
- 图片懒加载: 使用虚拟代理模式,先使用一个占位图片,当图片进入可视区域时再加载真实的图片。
以下是代码示例:
3.1 Vue3 响应式系统中的代理模式
Vue3 响应式原理中的数据劫持阶段是代理模式的经典实现。依赖收集和派发更新的过程中通过观察者模式实现。
以下是一个简化版的 Vue3 响应式系统示例:
1 | // 简化版响应式实现 |
3.2 接口数据的缓存优化
作用
缓存代理主要用于缓存和管理对某个对象的访问。它通过在代理对象中保存一个缓存,可以避免重复执行昂贵的操作,从而提高程序的性能。 在JavaScript中,缓存代理常用于网络请求、计算密集型操作、文件读写等场景。
它的工作原理是,在代理对象中保存之前执行过的操作结果,并根据请求的参数判断是否直接返回缓存结果,还是执行真实的操作并将结果缓存起来供后续使用。
核心思想
缓存代理的核心思想是延迟执行,即只有在必要时才执行真实的操作。代理对象会先检查缓存中是否已有请求的结果,如果有,则直接返回缓存的结果;如果没有,则执行真实的操作,并将结果缓存起来以供后续使用。
缓存代理模式可以提高程序的效率,尤其是对于一些昂贵的计算或网络请求等操作。它可以减少网络请求次数,降低服务器压力,并且对于一些不会频繁变动的数据,可以通过缓存避免重复计算,提高性能。
注意
需要注意的是,在使用缓存代理模式时,要注意缓存的更新和过期策略,以保证缓存的准确性和时效性。同时,对于一些频繁变动的数据,缓存代理可能会导致数据不实时,需要根据具体场景和需求来选择是否使用缓存代理。
当使用 JavaScript 进行网络请求时,可以使用缓存代理模式来缓存数据,避免重复请求相同的资源,提高性能和减少网络负载
1 | // 接口数据缓存代理 |
3.3 虚拟代理实现图片懒加载
作用
虚拟代理主要用于延迟和优化某些昂贵或复杂的操作,直到真正需要时才执行。
在JavaScript中,虚拟代理常用于需要耗费资源的操作,例如加载大量的图片、请求网络资源或执行复杂的计算。虚拟代理将这些操作延迟到真正需要使用结果或展示时才执行,通过代理对象来提供占位符或默认值。
核心思想
虚拟代理的核心思想是在代理对象中保存真实对象的引用,并根据需要决定是否加载、执行真实操作。当请求到达代理对象时,代理对象会判断是否需要或执行真实操作,如果需要,则加载执行真实操作,并返回结果;如果不需要,则返回占位符或默认值。
虚拟代理可以有效提高程序的性能和用户体验。通过延迟加载大型资源,可以减少页面的加载,并节省带宽和消耗。通过延迟执行复杂计,可以降低耗时操作对页面的影响,并提升用户交互的流畅性。
注意
需要注意的是,在使用虚拟代理时,要根据实际需要和场景合理使用,避免过度延迟或过度优化。同时,对于一些需要实时更新或即时加载的数据,不适合使用虚拟代理,需要根据具体需求来选择合适的代理模式。

1 | //真实对象 |
3.4 代理模式的优缺点分析
代理模式的优点
- 隐藏真实对象:代理模式可以隐藏真实对象的具体实现细节,只暴露必要的接口给客户端,从而保护了真实对象的安全性和隐私性。
- 控制对真实对象的访问:代理对象可以对客户端对真实对象的访问进行控制和限制,例如根据访问权限、时间等条件进行限制。
- 增加附加功能:代理对象可以在执行真实对象的操作前后添加额外的逻辑,从而增加了额外的功能。比如在真实对象操作前插入日志、缓存结果等。
- 惰性加载:代理对象可以延迟加载真实对象,直到真正需要时才进行实例化。这样可以节省系统资源,并提升系统的响应速度。
代理模式的缺点
- 增加复杂性:引入代理对象会增加代码复杂性,因为需要维护代理对象和真实对象之间的关系,同时需要处理代理对象和真实对象之间的通信,可能会导致代码变得复杂和难以维护。
- 增加开销:代理模式会引入额外的开销,包括额外的对象创建和通信开销。这可能会导致一定程度的性能损失。
- 可能降低效率:代理模式中的一些额外功能可能会导致性能降低,特别是对于需要频繁访问真实对象的操作。如果代理对象的额外功能耗费过多的时间和资源,可能会影响系统的整体性能。
代理模式在一些特定的场景下非常有用,可以提供额外的控制、保护和功能扩展。但是在一般情况下,如果不需要上述功能,引入代理模式可能会增加代码复杂性和开销,因此需要根据具体情况进行评估和选择。
4 总结与展望
代理模式作为一种常见的设计模式,在JavaScript开发中发挥着重要的作用。
通过代理对象,我们可以隐藏真实对象的具体实现细节,提高了系统的封装性和安全性。代理对象还可以在调用真实对象的操作前后进行额外的处理逻辑,实现功能的扩展和增强。同时,代理模式还可以延迟对真实对象的访问和操作,提升系统的性能和响应速度。
然而,代理模式也存在一些不足之处,包括增加代码复杂性、增加系统复杂性和可能引起性能损失等。在实际开发中,我们需要根据具体的业务场景和需求来判断是否适合使用代理模式,并综合考虑其优缺点。
总的来说,代理模式可以提高系统的封装性、安全性和性能,同时也可以扩展和增强系统的功能。了解代理模式,对于开发优秀的JavaScript应用程序是非常有益的