尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

使用 Proxy 与 EventTarget 在 JavaScript 中实现 Observable 观察者模式

使用 Proxy 与 EventTarget 在 JavaScript 中实现 Observable 观察者模式 教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载导读本文基于 30-seconds-of-code 仓库中的 observable-proxy.md 一文系统讲解如何利用 JavaScript 内置的Proxy对象set陷阱与EventTarget接口以极少量代码实现经典的可观察Observable观察者模式。你将学会在浏览器与 Node.js 之间通用的响应式状态通知方案掌握Proxy构造器返回、CustomEvent事件载荷传递等关键技巧并能在此基础上进一步扩展嵌套对象、数组乃至迷你 Redux 式状态仓库。一、观察者模式与 JavaScript 的落地思路观察者模式Observer pattern是一种行为型设计模式一个对象称为subject / 主题维护着一份dependents观察者列表当主题的状态发生变化时会逐一通知这些观察者。从本质上说它就是一个简单的pub/sub发布–订阅系统——发布者发出变更订阅者响应变更。在 JavaScript 中原生并没有单独的 Observable 类但我们可以把三件现成的语言能力组合起来EventTarget浏览器 DOM 与 Node.js 中都存在的接口提供addEventListener()/removeEventListener()/dispatchEvent()天然就是一套事件订阅发布设施ProxyES6 引入的对象包装器可以在目标对象上拦截set、get等基本操作称为trap / 陷阱从而在属性被赋值时插入自定义逻辑CustomEvent允许携带自定义数据detail的事件对象。这三者的组合方式非常自然用Proxy的set陷阱拦截属性赋值赋值发生时构造一个CustomEvent广播出去EventTarget负责把事件派发给所有注册的监听器。[!NOTE] 该方案选用EventTarget是因为它在浏览器与 Node.js 之间通用。如果只面向 Node.js 环境EventEmitter是更健壮的替代方案但需要对该实现做少量调整将addEventListener/dispatchEvent换成on/emit等 API。二、核心实现Observable类在 30-seconds-of-code 仓库的 observable-proxy.md 中实现被收敛在一个短小的类里class Observable extends EventTarget { constructor() { super(); return new Proxy(this, { set: (target, property, value) { target[property] value; this.dispatchEvent(new CustomEvent(property, { detail: value })); return true; }, }); } }逐行拆解其工作原理继承EventTargetclass Observable extends EventTarget让每个实例都拥有完整的监听与派发能力super()先完成基类初始化。构造器返回Proxyreturn new Proxy(this, handler)是这里最关键的一步。正常情况下构造器返回this但 JavaScript 允许构造器显式返回一个对象从而让new Observable()的结果直接是一个代理。仓库中的姊妹文章 cannot-extend-proxy-object.md 专门解释过Proxy本身是异质对象exotic object无法被子类真正继承从构造器返回代理就是绕过该限制的标准做法。set陷阱拦截赋值set: (target, property, value) { ... }会在任何subject.someProp value形式的赋值时被触发。写入真实值target[property] value;先把值落到目标对象即this上保证代理与真实对象的数据一致。构造变更事件new CustomEvent(property, { detail: value })把属性名作为事件的type把新值作为detail载荷——监听器正是通过event.type区分监听的是哪个属性通过event.detail拿到最新值。派发事件this.dispatchEvent(...)经由EventTarget通知所有已注册的监听器。返回trueset陷阱必须返回布尔值。返回true表示赋值成功严格模式下返回false会抛出TypeError这是 Proxy 规范要求的契约。仓库中 trigger-event.md 对CustomEvent的用法有更详细的补充说明CustomEvent构造器接收eventType与detail两个参数detail用于向监听器传递任意自定义数据这正是上述实现传递新值的依据。三、使用与验证订阅、触发、解绑3.1 监听属性变化创建一个Observable实例然后像操作普通对象一样给它赋值就能触发对应事件const subject new Observable(); subject.addEventListener(name, event { console.log(Name changed to ${event.detail}); }); subject.name Alice; // Name changed to Alice这里的语义非常直观subject.name Alice这一行同时完成了三件事——写入属性、以name为类型广播CustomEvent、把Alice作为detail交给监听器。监听器无需知道值何时变化只要订阅了name事件即可。3.2 订阅多个属性因为事件类型即属性名所以可以按属性粒度分别订阅互不干扰subject.addEventListener(age, event { console.log(Age changed to ${event.detail}); }); subject.name Bob; // Name changed to Bob subject.age 30; // Age changed to 30未被监听的其他属性赋值不会产生任何副作用监听器数量、监听顺序均由EventTarget统一管理。3.3 取消订阅Observable继承自EventTarget因此同样支持removeEventListener()解除监听。注意传参必须与订阅时保持一致相同的事件类型与相同的函数引用例如const onNameChange event { console.log(Name changed to ${event.detail}); }; subject.addEventListener(name, onNameChange); subject.removeEventListener(name, onNameChange); subject.name Carol; // 不再输出任何日志仓库中关于事件监听的更多细节可参考 add-remove-event-listener.md含{ once: true }一次性监听等技巧与 add-remove-multiple-event-listeners-single-element.md批量注册/解绑多个监听器。四、set 陷阱与 Proxy 机制的原理回顾上面的实现之所以短小精悍依赖的是Proxy强大的拦截能力。根据仓库中的 proxy-introduction.md 一文Proxy的通用定义是const proxy new Proxy(target, handler);其中target是被包装的目标对象handler是包含一个或多个陷阱函数的对象。set(target, prop, value, receiver)正是其中之一它拦截target[prop] value这样的赋值操作——本篇文章的Observable只用了这一个陷阱但Proxy还提供get、has、apply、construct、defineProperty、deleteProperty、ownKeys等十余个陷阱可覆盖几乎所有对象基本操作。同一仓库中的两个姊妹实现可以帮你建立更完整的上下文dynamic-getter-setter-proxy.md同时使用get与set陷阱实现动态读写其中set陷阱通过返回false拒绝非法赋值如只读属性、格式不匹配的键名与本篇set陷阱返回true形成对照object-array-proxy.md在get陷阱中按属性名分发数组式方法展示Proxy作为外观层的另一种应用。由此可见set陷阱返回值的语义是这套 API 的关键约束返回true表示接受赋值返回false表示拒绝。我们的Observable在写入真实值后返回true保证任何赋值都正常生效只是顺带广播了事件。五、扩展实战嵌套对象、数组与可观察状态仓库原文指出这套代码虽然简单但极易扩展以处理更复杂的场景例如嵌套对象或数组甚至可以构建一个类似 Redux 的可观察状态仓库。下面给出两个可运行的扩展方向。5.1 递归代理嵌套对象让set陷阱在遇到对象值时递归包装使得深层属性赋值也能触发事件class Observable extends EventTarget { constructor(target {}) { super(); return new Proxy(target, { set: (obj, property, value) { if (value typeof value object) value new Observable(value); // 递归代理嵌套对象 obj[property] value; this.dispatchEvent(new CustomEvent(property, { detail: value })); return true; }, }); } } const state new Observable({ user: {} }); state.addEventListener(user, event { console.log(User changed to, event.detail); }); state.user { name: Alice }; // User changed to { name: Alice }[!NOTE] 深度递归代理是扩展方向之一但需要注意监听器若关心state.user.name这类深层路径的变动还需为每一层递归代理单独订阅事件若希望所有深层变更都汇聚到根节点则需要设计统一的事件路由策略。5.2 数组的可观察化同样借助set陷阱拦截数组索引与length的变化若希望push/pop等变更方法也触发通知可在set中检测length变化class ObservableArray extends EventTarget { constructor(arr []) { super(); return new Proxy(arr, { set: (target, property, value) { const changed target[property] ! value; target[property] value; if (changed) this.dispatchEvent(new CustomEvent(property, { detail: value })); return true; }, }); } } const list new ObservableArray([1, 2, 3]); list.addEventListener(length, event { console.log(Length is now ${event.detail}); }); list.push(4); // 触发 length 变更事件5.3 迷你可观察状态仓库类 Redux 风格将多个可观察属性汇聚成一个 store 对象对外暴露getState与基于dispatch的变更入口任何状态变更都统一广播class ObservableStore extends EventTarget { constructor(initialState {}) { super(); this._state new Observable(initialState); } getState() { return this._state; } dispatch(action) { // 此处省略 reducer 逻辑仅演示事件广播 this.dispatchEvent(new CustomEvent(action, { detail: action })); } }以上扩展的核心思路是状态即事件源、属性名即事件类型、新值即事件载荷。只要守住这三条约定就能把任意数据结构接入这套可观察体系。六、在事件驱动与 Signals 之间建立联系本篇实现与仓库中另一篇进阶文章 event-driven-vanilla-js-signals.md 一脉相承。后者以纯事件驱动方式实现了signals 与 reactivity响应式编程用Signal类同样继承EventTarget管理值并派发notify事件用Effect类管理订阅与清理用ComputedValue组合两者实现派生值。两者对比可以得出清晰的取舍维度本篇 ObservableProxy 版Signals 版纯事件驱动变更检测Proxy的set陷阱自动拦截任何赋值显式调用set value或 setter事件类型属性名name、age统一事件名notify订阅方式addEventListeneraddEventListenerEffect封装去重判断未内置值相同则跳过派发扩展性适合通用对象任意属性监听适合受控的状态流与派生计算换句话说若你希望任何对象属性一赋值就自动通知Observable代理是零侵入的答案若你更在意值的去重、派生值自动更新与显式的订阅生命周期管理则可参考 Signals 方案的思路。七、小结本文从 observable-proxy.md 出发完整呈现了用Proxy的set陷阱配合EventTarget/CustomEvent实现观察者模式的经典写法并围绕同一仓库中的 proxy-introduction.mdProxy 陷阱总览、cannot-extend-proxy-object.md构造器返回 Proxy 的合法性、trigger-event.mdCustomEvent 用法等文章与源码对原理、边界与扩展方向做了深度佐证。核心要点回顾观察者模式本质是 pub/subEventTarget提供订阅/派发基础Proxy提供自动拦截CustomEvent负责携带属性名与新值构造器显式return new Proxy(...)是利用 Proxy 实现可继承类的关键手法set陷阱须返回布尔值true表示接受赋值该模式可自然扩展到嵌套对象、数组与类 Redux 状态仓库与仓库中基于纯事件驱动的 Signals 方案互为补充可根据是否希望自动拦截赋值来选择。把属性赋值这一最普通的操作升级为可观察事件源是这门技术最有趣的地方——试着把它接进你的 UI 状态管理或数据流层看看能走多远。赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐终极指南如何用Vital光谱变形波表合成器创造专业音效终极指南如何用Vital光谱变形波表合成器创造专业音效 想要创造出令人惊艳的电子音乐音色吗渴望突破传统合成器的限制探索声音设计的无限可能吗今天我要为你介音视频WaveDrom性能优化大型时序图渲染与内存管理技巧WaveDrom性能优化大型时序图渲染与内存管理技巧 WaveDrom作为一款强大的数字时序图编辑器在处理复杂时序图时可能会遇到性能挑战。本文将分享针对大型开发工具硬件开发优化GLM-4.7_vLLM-ascend推理效率Flashcomm1与balance_scheduling应用优化GLM 4.7_vLLM ascend推理效率Flashcomm1与balance_scheduling应用 GLM 4.7_vLLM ascend是基于人工智能大模型深度学习上一篇Windows热键冲突终极指南如何用Hotkey Detective一键精准定位占用程序下一篇思源宋体让中文排版变得优雅又简单创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表