尧图精选

前端精读周刊:深入理解 Proxy 代理模式——从按需渲染到响应式双向绑定

🕒 发布时间:2026/10/2 19:21:06 📁 来源:尧图网络
文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载导读本文基于前端精读周刊 178.精读《设计模式 - Proxy 代理模式》 展开系统讲解结构型设计模式中的 Proxy代理模式它通过一个代理对象代为控制对原始对象的访问从而在不侵入原始对象的前提下完成按需加载、访问权限控制与响应式绑定。你将通过三个贴近日常开发的实战场景、完整的 TypeScript 示例以及本仓库中 react-easy-state 源码与 Vue 3 依赖收集机制的印证理解代理模式的适用边界、实现要点与常见弊端并学会在恰当时机运用它。一、什么是 Proxy 代理模式Proxy代理模式属于结构型设计模式其核心做法是通过访问代理对象来代替访问原始对象从而在设计上获得额外的灵活性。它的意图可以概括为一句话为其他对象提供一种代理以控制对这个对象的访问。这句话是代理模式的纲领我们关心的不是代理如何转发请求而是为什么需要控制访问以及在代理层可以做什么额外的事。理解了这两点才算是真正掌握了代理模式。从本仓库 178.精读《设计模式 - Proxy 代理模式》 的观点看代理模式真正的难点不在于理解它如何工作而在于理解哪些场景适合用代理——创建了代理对象之后怎么用才能发挥它的价值。二、三个实战场景什么时候该用代理设计模式需要在日常工作里用起来结合例子才能加深理解。下面三个场景分别对应代理模式的三大典型用途。场景一获得文本对象长度按需使用优化耗时获得一个文本对象的长度必须真正把文本渲染出来而渲染是比较耗时的操作。业务上我们可能只在某些场景下才需要访问文本长度更多时候只需要读取文本内容——这两种操作的耗时完全不同。如何做到业务层调用无感知却能优化执行耗时代理模式可以解决把业务层使用的文本对象替换为代理对象代理对象初始化时并不渲染文本而是在调用文本长度时才真正渲染。这对应代理模式的第一类典型场景对开销大的对象使用代理实现按需使用懒加载。访问者始终面对同一个接口感知不到渲染被推迟到了最后一刻。场景二对象访问保护代理层做权限控制某个大型系统开发完成后突然要求增加代码访问权限体系不同模块对同一个底层对象拥有不同的访问权限。如果把这个权限控制逻辑写进底层对象就会违背开闭原则对扩展开放、对修改关闭对象本身的实现也不再纯粹维护成本随之上升。如何做到不修改对象本身实现权限控制代理模式同样可以解决将底层对象的导出替换为代理对象由代理对象统一控制访问权限。底层对象保持纯净权限逻辑收敛在代理层新增权限规则时只需修改代理符合开闭原则。这对应代理模式的第二类典型场景对需要保护的对象进行代理在代理层做权限控制。场景三对象与视图双向绑定访问/修改时执行额外逻辑Angular、Vue 这类前端框架采用双向绑定视图更新技术对象被修改后使用到该对象的视图会自动刷新。要实现这一点需要做到两件事在对象被访问时记录调用了它的视图绑定关系在对象被修改时刷新所有调用它的视图。问题在于如果把这些逻辑直接插到业务代码访问与修改对象的每个地方维护成本会极其巨大。如何做到业务层无感知代理模式给出了很好的答案业务层拿到的对象其实已经是代理对象它在被访问get与被修改set时都会执行固定的钩子完成视图绑定与视图刷新。这对应代理模式的第三类典型场景在对象访问与修改时执行其他逻辑适合收敛在代理层做。双向绑定几乎是代理模式最好的现实例子。关联印证本仓库 185.精读《设计模式 - Observer 观察者模式》 明确指出双向绑定概念本身属于观察者模式而代理只是实现双向绑定的一种具体方案。两个模式一个描述该做什么概念一个给出怎么做实现配合阅读更完整。三、意图解释与使用要点代理模式的意图很容易理解就是通过代理对象代替原始对象的访问。但这只是实现方式真正要掌握的是使用场景的判断。综合上面的例子可以把适合使用代理的场景归纳为三条对开销大的对象使用代理实现按需使用懒加载避免不必要的开销对需要保护的对象进行代理在代理层做权限控制保持底层对象纯净在对象访问与修改时执行额外逻辑把这类横切逻辑收敛在代理层业务代码无感知。判断是否使用代理核心是问自己我是否希望在不修改原始对象的前提下为它的访问过程附加行为如果答案是肯定的代理模式就是一个值得考虑的方案。四、结构图与角色划分代理模式涉及三个核心角色角色说明Subject定义RealSubject与Proxy共用的接口保证任何使用RealSubject的地方都可以无缝替换为ProxyRealSubject原始对象即真正执行业务逻辑的对象Proxy代理实体持有对RealSubject的引用并在访问前后附加额外逻辑使用时关系如下客户端要访问subject时第一层访问的是Proxy代理代理在转发前可以执行拦截逻辑懒加载、权限校验、依赖收集等代理将realSubject转发给客户端。由于Proxy与RealSubject实现同一个Subject接口客户端完全无感知这是代理模式透明替换的关键。从结构上看Proxy相当于在客户端与真实对象之间插入的一层拦截器这正是代理模式名字的由来。五、代码例子JS 的new Proxy下面例子使用 TypeScript 编写// 对象 obj const proxy new Proxy(obj, { get(target, key) {} set(target, key, value) {} })JS 创建代理还是蛮简单的new Proxy(target, handler)返回一个代理对象handler中的get陷阱可以控制对象所有成员属性包括成员变量与成员方法的访问set陷阱可以控制其修改。补充Reflect与get/set的正确写法从本仓库 24.精读《现代 JavaScript 概览》 可以补充一个重要细节ES6 中的 Proxy 通过 Proxy 方法实现对对象的一层拦截提供一种机制代理对象的操作。而 Reflect 是一个内置对象它提供可拦截 JavaScript 操作的方法方法与代理处理程序的方法一一对应。因此实际使用中get/set陷阱内部通常要调用Reflect.get/Reflect.set完成真正的读写同时能正确返回布尔值、维持this指向一个可实际运行的例子如下const proxy new Proxy(obj, { get(target, key) { console.log(读取属性: ${String(key)}) return Reflect.get(target, key) }, set(target, key, value) { console.log(修改属性: ${String(key)} ${value}) return Reflect.set(target, key, value) } })要点get(target, key)在任何成员访问时触发包括读取属性与调用方法set(target, key, value)在任何成员赋值时触发Reflect的陷阱方法与 Proxy 处理程序一一对应是代理内部转发读写操作的标准工具。双向绑定的最小实现把场景三的访问记录绑定 修改刷新视图落到代码上只需在get与set中分别加入绑定与刷新的钩子function observe(obj, onAccess, onMutate) { return new Proxy(obj, { get(target, key) { onAccess(key) // 记录哪个视图访问了哪个属性 return Reflect.get(target, key) }, set(target, key, value) { const result Reflect.set(target, key, value) onMutate(key) // 通知使用该属性的视图刷新 return result } }) }业务层照常读写对象完全感知不到代理层做的绑定与刷新——这就是业务层无感知的具体落地。关联印证本仓库 185.精读《设计模式 - Observer 观察者模式》 在弊端一节也演示了同样的思路——用new Proxy(obj, { get, set })实现观察者效果obj被任意组件访问时触发get完成视图绑定被更新时触发set刷新所有使用到的视图。正如该文所说使用设计模式切记不要死板理解原理就行了在不同平台有不同的更加优雅的实现方式。六、源码印证react-easy-state 如何用 Proxy 实现响应式本仓库 98.精读《react-easy-state 源码》 提供了一个绝佳的工业级案例react-easy-state利用 Proxy 创建了非常易用的全局数据流管理方式任何对store对象的修改都会让使用了该对象的组件自动重渲染。import React from react; import { store, view } from react-easy-state; const counter store({ num: 0 }); const increment () counter.num; export default view(() button onClick{increment}{counter.num}/button);其原理与上述代理模式完全同构只是把钩子换成了更复杂的依赖收集store底层是observable(obj)即用 Proxy 把普通对象变成可追踪对象。React Hooks 场景下会返回useMemo(() observable(obj), [])保证所有渲染周期内只在初始化时创建一次代理避免死循环view外层套memo类似PureComponent内部用observe包裹组件并配合scheduler与lazy两个参数——scheduler: () setState({})让 store 变化时强制重渲染组件lazy: true避免初始化时重复渲染batch解决连续修改对象导致多次渲染的批量合并问题核心是unstable_batchedUpdates(() ...)在其内执行的函数不会触发更新回调执行完毕再统一批量更新。这正是对象与视图双向绑定场景在生产库中的真实形态代理模式负责拦截get/set观察者机制负责通知与重渲染两者各司其职。七、源码印证Vue 3 依赖收集与 Mutable 更新本仓库 109.精读《Vue3.0 Function API》 从框架设计的角度印证了代理模式的价值Vue 利用 Proxy 监听机制可以做到setup函数不重新执行但 Template 重新渲染的效果。在 Vue 3 中Hooks 与 Mutable 深度结合通过包装x.valuex变更时引用保持不变、仅值发生变化。React 中useMouse修改值会导致整个组件函数重新执行而 Vue 借助 Proxy 的依赖收集setup只执行一遍Template 的重渲染完全由用到的值变了就重新渲染的机制驱动。这正是对象访问记录绑定、对象修改刷新视图在真实框架中的直接体现。八、弊端与使用边界代理模式并非没有代价本仓库原文档明确指出了两点微弱的性能开销代理会增加微弱的开销因此请不要将所有对象都变成代理。没有意义的代理只会徒增程序开销——代理的价值在于按需与拦截对不需要拦截的对象套代理纯属浪费。调试困难代理对象过多会导致调试困难。因为代理层的存在我们往往可能忽略这一层带来的影响甚至忘记这个对象其实是一个代理排查问题时容易被多出来的一层误导。从源码印证看这条边界在 98.精读《react-easy-state 源码》 中也有体现——它只对store()显式包裹的对象做代理而不会对每个渲染中的临时对象滥用代理同时提供batch机制抵消代理频繁触发带来的渲染开销。代理是按需施加的拦截层不是默认包装。九、总结代理与继承、动态代理与静态代理代理和继承有足够多的相似之处继承中子类几乎可以认为是对父类的代理子类可以重写父类的方法。但两者仍有本质区别如果不用new Proxy这种 API 创建代理而是采用继承的方式实现你会一下子继承这个类的所有方法做不到按需控制访问权限的灵活效果因此代理比继承更加灵活代理可以只拦截关心的操作其余操作原样转发。另外JS 的new Proxy对应的是Java 动态代理模式一般认为动态代理比静态代理更强大——因为动态代理在运行时生成、可按需定制拦截逻辑而静态代理在编译期就已固定。最后再重申那句话代理模式的理解与运用并不难难就难在能否在恰当的场合想到它。按需渲染懒加载、权限控制、响应式双向绑定是三个最典型的恰当的场合当你需要不修改对象、却要在访问它时附加行为时不妨先想想代理模式。至于底层细节可以继续阅读本仓库的 98.精读《react-easy-state 源码》 与 109.精读《Vue3.0 Function API》 深入理解。赞分享文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载相关推荐前端精读周刊前端异步编程模式前端精读周刊前端异步编程模式 引言 你是否还在为回调地狱而烦恼是否在Promise链式调用中迷失方向是否对async/await的性能陷阱感到困惑本文将文档技术博客教程前端精读周刊深入理解 JavaScript Pipe Operator|提案前端精读周刊深入理解 JavaScript Pipe Operator| 提案 本文源于前端精读周刊第 228 期围绕 TC39 的 Pipe Oper文档技术博客教程前端精读周刊深入理解 JavaScript 迭代器 Iterable 协议与实战应用前端精读周刊深入理解 JavaScript 迭代器 Iterable 协议与实战应用 本篇精读源自 前沿技术/262.精读《迭代器 Iterable》.md文档技术博客教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →