当前位置:首页 > 技术 > 正文内容

JavaScript 实现观察者模式

访客 技术 2026年6月1日 1

模式概述

观察者模式属于行为型设计模式,核心在于构建一对多的依赖模型:当目标对象状态发生变动,所有关联的监听对象将自动获得通知并执行相应处理。

组成要素

  • Publisher(发布者):管理订阅者集合,负责注册、取消注册及消息广播
  • Subscriber(订阅者):声明接收变更通知的标准接口
  • ConcretePublisher(具体发布者):承载业务状态,状态变更时触发广播
  • ConcreteSubscriber(具体订阅者):实现通知接口,完成自身状态与发布者的同步

完整代码实现

// 发布者抽象层
class Publisher {
  constructor() {
    this.subscribers = new Set();
  }

  subscribe(listener) {
    if (typeof listener?.onMessage !== 'function') {
      throw new TypeError('订阅对象必须实现 onMessage 方法');
    }
    this.subscribers.add(listener);
    return () => this.unsubscribe(listener);
  }

  unsubscribe(listener) {
    this.subscribers.delete(listener);
  }

  broadcast(payload) {
    this.subscribers.forEach(listener => listener.onMessage(payload));
  }
}

// 订阅者抽象层
class Subscriber {
  onMessage(payload) {
    throw new Error('子类必须重写 onMessage 方法');
  }
}

// 业务实现:库存监控模块
class InventoryTracker extends Publisher {
  #stock = 0;

  setStock(quantity) {
    const prev = this.#stock;
    this.#stock = quantity;
    if (prev !== quantity) {
      this.broadcast({ current: quantity, previous: prev, timestamp: Date.now() });
    }
  }

  get stock() {
    return this.#stock;
  }
}

// 业务实现:邮件通知订阅方
class EmailNotifier extends Subscriber {
  onMessage({ current, previous }) {
    console.log(`[邮件服务] 库存从 ${previous} 调整为 ${current}`);
  }
}

// 业务实现:短信预警订阅方
class SmsAlert extends Subscriber {
  constructor(limit = 10) {
    super();
    this.limit = limit;
  }

  onMessage({ current }) {
    if (current < this.limit) {
      console.log(`[短信预警] 库存告急,剩余 ${current} 件,请尽快补货`);
    }
  }
}

调用演示

const tracker = new InventoryTracker();
const email = new EmailNotifier();
const sms = new SmsAlert(15);

// 注册订阅
const unsubscribeEmail = tracker.subscribe(email);
tracker.subscribe(sms);

tracker.setStock(20);  // [邮件服务] 库存从 0 调整为 20
tracker.setStock(12);  // [邮件服务] 库存从 20 调整为 12
                       // [短信预警] 库存告急,剩余 12 件,请尽快补货

// 取消邮件订阅
unsubscribeEmail();

tracker.setStock(8);   // [短信预警] 库存告急,剩余 8 件,请尽快补货

核心优势分析

降低耦合强度

发布方仅维护订阅者列表的引用,无需了解具体订阅者的业务逻辑。订阅方可独立演进,不影响发布方代码。

动态关系绑定

运行时自由增减订阅者,系统灵活性显著提升。一对多的通知机制避免了显式遍历调用。

状态自动同步

消除手动同步各对象状态的繁琐操作,降低因遗漏更新导致的数据不一致风险。

与发布/订阅模式的对比

维度 观察者模式 发布/订阅模式
架构关系 发布者直接持有订阅者引用 引入事件总线作为中介
耦合程度 松耦合,彼此知晓存在 完全解耦,互不可见
交互方式 直接调用订阅者的方法 通过主题通道传递消息
典型场景 组件内部状态联动 跨模块、跨系统的消息通信

增强功能扩展

// 支持异步广播,避免阻塞主流程
async broadcastAsync(payload) {
  const tasks = Array.from(this.subscribers).map(
    listener => Promise.resolve().then(() => listener.onMessage(payload))
  );
  await Promise.allSettled(tasks);
}

// 支持按条件过滤广播对象
broadcastWhere(payload, predicate) {
  this.subscribers.forEach(listener => {
    if (predicate(listener)) {
      listener.onMessage(payload);
    }
  });
}

// 支持订阅优先级(高优先级先执行)
subscribeWithPriority(listener, priority = 0) {
  listener._priority = priority;
  this.subscribers.add(listener);
  this._sortByPriority();
}

_sortByPriority() {
  this.subscribers = new Set(
    [...this.subscribers].sort((a, b) => (b._priority || 0) - (a._priority || 0))
  );
}

浏览器原生 API 中的观察者

// ResizeObserver:监听元素尺寸变化
const ro = new ResizeObserver(entries => {
  for (const entry of entries) {
    const { width, height } = entry.contentRect;
    console.log(`元素尺寸: ${width}x${height}`);
  }
});
ro.observe(document.querySelector('.responsive-box'));

// PerformanceObserver:监听性能指标
const po = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    console.log('性能条目:', entry.name, entry.duration);
  }
});
po.observe({ entryTypes: ['measure', 'mark'] });

工程实践建议

在构建复杂应用时,若需处理高频数据流、异步编排或背压控制,推荐采用专业响应式库:

  • RxJS:提供丰富的操作符与调度策略,适合复杂事件流处理
  • MobX:基于代理的自动依赖追踪,简化状态与视图的绑定

对于简单场景,手写观察者模式足以满足需求,能有效控制依赖体积。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。