HTML本身不支持观察者模式,需用JavaScript实现:MutationObserver监听DOM变化,Custom Elements的observedAttributes响应属性变更,Proxy拦截对象状态,EventTarget用于松耦合通信。

HTML 本身不提供观察者模式能力,所谓“用 HTML 构建观察者模式组件”,本质是用 JavaScript 在 HTML 环境中实现观察逻辑,并绑定到 DOM 元素或自定义组件上。直接写 <observer> 标签或靠 HTML 属性自动触发通知,是不可能的。
MutationObserver 是监听 DOM 变化的唯一标准方式
当你需要知道某个元素内部是否被插入、删除、属性是否被改、文本是否更新,MutationObserver 是浏览器原生支持且性能可控的方案。它不是“HTML 的功能”,但它是你在 HTML 页面里能用的最贴近“观察 DOM”的机制。
- 必须显式调用
observe()才会开始监听;不调就完全静默 - 回调接收的是
MutationRecord[]数组,不是单个事件对象,需遍历判断record.type(如'childList'、'attributes') -
subtree: true才能监听子树变动;默认只看目标节点自身 - 监听目标被移除后,若没调
disconnect(),MutationObserver实例仍持有对节点的引用,可能阻碍 GC
Custom Elements + observedAttributes 实现属性驱动的响应式组件
如果你在写自定义 HTML 标签(比如 <my-counter>),想让组件对 value 属性变化做出反应,不能指望属性变就自动触发重渲染——必须手动声明哪些属性要被观察,并在 attributeChangedCallback 中处理。
- 静态 getter
observedAttributes返回字符串数组,例如return ['value', 'disabled'] - 只有通过
el.setAttribute('value', '10')或el.value = '10'(后者需同步反射为属性)才会触发attributeChangedCallback - HTML 属性和 JS 属性不自动同步:设
el.value = 5不会触发回调,除非你同时调el.setAttribute('value', '5')或在 setter 里手动同步 -
attributeChangedCallback中拿到的是字符串值,数值型属性需自行parseInt或Number
Proxy 封装数据对象,配合事件分发实现业务层观察
当你要观察的是 JS 对象状态(如 { count: 0, name: 'foo' }),而不是 DOM 节点,Proxy 是目前最实用的拦截手段。它比 Object.defineProperty 更全面,能捕获新增、删除、遍历等操作。
立即学习“前端免费学习笔记(深入)”;
-
set拦截赋值,deleteProperty拦截delete obj.key,ownKeys拦截Object.keys等 - 不要在
set里直接更新 UI;应调用统一的通知函数(如notify({ type: 'set', key, value })),由订阅者决定怎么响应 -
Proxy无法代理原始值(string、number)、null或undefined;数组索引赋值(arr[0] = x)也不会触发set,需配合Reflect.set和额外判断 - 一个
Proxy实例只能代理一个对象,不能复用到多个目标上
EventTarget 适合松耦合通信,但不是严格意义上的 Observer 模式
EventTarget(包括 HTMLElement)提供 dispatchEvent / addEventListener,常被当作简易事件总线使用。但它缺少 Observer 模式的关键特征:没有中心化的 Subject 管理订阅关系,也不保证通知顺序或错误隔离。
- 它不记录谁订阅了什么,
removeEventListener必须传入**完全相同的函数引用**才能解绑,匿名函数或箭头函数会导致无法清理 - 事件冒泡/捕获机制与观察者模式的“一对多直连通知”逻辑不同,容易误用为跨层级通信,增加调试难度
- 适合组件间低耦合广播(如
document.dispatchEvent(new CustomEvent('theme-change', { detail: 'dark' }))),不适合精确控制通知时机或依赖状态的场景 - 若需强类型、可取消、可链式组合的通知流,应引入 RxJS 的
Subject或手写带unsubscribe的观察者管理器
真正难的不是写出一个能 notify 的类,而是决定观察什么、谁该被通知、何时该停止监听。DOM 变动、属性变更、数据赋值、自定义事件——每种场景对应不同的 API 边界和内存责任,混用容易导致响应延迟或泄漏。选哪个,取决于你实际要“观察”的东西落在哪一层。



















