You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于JavaScript观察者模式中notify方法必要性的技术疑问

观察者模式的核心意义与你的示例解析

你疑惑为什么不直接调用函数而是用observable.notify(),本质是没get到观察者模式的核心价值——解耦事件触发端与逻辑处理端,下面结合你的例子拆解:

1. 避免硬绑定,符合开放封闭原则

如果直接在handleClick里写logger()和toastify(),那么按钮点击事件就和这两个函数死死绑在一起了:

  • 以后要加新功能(比如新增发送用户行为统计的函数),你必须修改handleClick的代码,把新函数加进去;
  • 要是想临时关闭某个功能(比如暂时不打日志),你得注释掉logger()的调用,或者加一堆条件判断。

而用观察者模式的话,所有处理逻辑都通过subscribe注册到observable上,handleClick只需要触发notify就行:

  • 加新功能?只需要多一行observable.subscribe(新函数),完全不用碰事件处理代码;
  • 关功能?调用observable.unsubscribe(要移除的函数)即可,事件逻辑不受影响。

2. 统一管理多逻辑,代码更简洁

当一个事件需要触发多个操作时(比如你的例子里同时打日志+弹提示),直接调用的写法会让事件处理函数变得臃肿:

function handleClick(){
  logger('User clicked button');
  toastify('User clicked button');
  // 以后加新逻辑还要继续堆在这里
}

用notify()的话,所有订阅的函数会自动批量执行,你不用在事件处理函数里逐个罗列,代码更干净,也不容易漏写。

3. 支持动态调整订阅关系

观察者模式允许你在运行时动态添加或移除订阅者,这在复杂场景下非常有用。比如:

  • 用户切换到“静默模式”时,移除toastify的订阅,不再弹出提示;
  • 进入调试模式时,新增一个调试日志的订阅函数。

这些操作都不需要修改事件触发的代码,只需要操作observable的订阅关系即可。


直观对比示例

直接调用的耦合写法

import { ToastContainer, toast } from "react-toastify";

function logger(data) {
  console.log(`${Date.now()} ${data}`);
}

function toastify(data) {
  toast(data);
}

// 新增统计函数
function sendAnalytics(data) {
  console.log(`Sending analytics: ${data}`);
}

export default function App() {
  function handleClick() {
    // 每次加新功能都要修改这里
    logger('User clicked button!');
    toastify('User clicked button!');
    sendAnalytics('User clicked button!');
  }

  function handleToggle() {
    logger('User toggled switch!');
    toastify('User toggled switch!');
    sendAnalytics('User toggled switch!');
  }

  return (
    <div className="App">
      <Button onClick={handleClick}>Click me!</Button>
      <FormControlLabel control={<Switch onClick={handleToggle} />} />
      <ToastContainer />
    </div>
  );
}

观察者模式的解耦写法

import { ToastContainer, toast } from "react-toastify";

// 基础Observable实现(示例用)
class Observable {
  constructor() {
    this.subscribers = [];
  }
  subscribe(fn) {
    this.subscribers.push(fn);
  }
  unsubscribe(fn) {
    this.subscribers = this.subscribers.filter(sub => sub !== fn);
  }
  notify(data) {
    this.subscribers.forEach(fn => fn(data));
  }
}

const observable = new Observable();

function logger(data) {
  console.log(`${Date.now()} ${data}`);
}

function toastify(data) {
  toast(data);
}

// 新增统计函数,仅需订阅,无需修改事件逻辑
function sendAnalytics(data) {
  console.log(`Sending analytics: ${data}`);
}

observable.subscribe(logger);
observable.subscribe(toastify);
observable.subscribe(sendAnalytics);

// 临时移除日志功能,不影响事件处理
// observable.unsubscribe(logger);

export default function App() {
  function handleClick() {
    // 无论加多少订阅者,这里永远只需一行代码
    observable.notify("User clicked button!");
  }

  function handleToggle() {
    observable.notify("User toggled switch!");
  }

  return (
    <div className="App">
      <Button onClick={handleClick}>Click me!</Button>
      <FormControlLabel control={<Switch onClick={handleToggle} />} />
      <ToastContainer />
    </div>
  );
}

内容的提问来源于stack exchange,提问作者Sachin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 06:15:37