JavaScript 中的观察者设计模式

2025-05-26

JavaScript 中的观察者设计模式

在使用任何语言时,我们都会倾向于使用一些可复用的设计方案来解决常见问题。在 JavaScript 中,我们也混合使用了一些定义明确的模式。

观察者模式就是其中之一。

在本文中,我们将进一步了解 JavaScript 中的观察者设计模式,并在原始 JavaScript 中实现一个小示例。


什么是观察者设计模式?

观察者模式遵循订阅模型。订阅者(通常称为观察者)订阅由发布者(通常称为主体)处理的事件或操作,并在事件或操作发生时收到通知。

主体向所有观察者广播事件或动作的发生。

当观察者不再希望收到主题的变更通知时,它会取消对主题的订阅,然后主题会将其从订阅者列表中删除。

观察者设计模式与发布者/订阅者模式非常相似,但有一点不同,即发布者/订阅者模式还指定了它想要订阅的主题。

例如,当检测键盘快捷键时,订阅者可以选择在发布者/订阅者模型中指定它想要监听的组合键


观察者模式的实现

作为观察者模式的一个示例,我们将实现一个简单的交互,其中多个元素监听屏幕上的鼠标位置并执行不同的操作。

下面是我们互动的示例:

演示互动

在我们实现这个交互之前,让我们先分析一下这个例子中鼠标位置改变时发生了什么。

  • 鼠标位置会立即在右上角的文本框中更新。

  • 圆圈在延迟 1 秒后跟随鼠标的轨迹。

从上面的描述中,我们看到多个组件需要有关同一事物的信息,但行为却不同。

从上面的例子中,我们可以看出,主体监听窗口上的鼠标事件,并将其转发给任何需要它的对象。在上面的例子中,圆圈和文本框就是观察者。

因此现在让我们开始实施它。

步骤 1. 实现 MousePositionObservable 类

首先,让我们实现这个MousePositionObservable类。这个类需要做以下事情:

  • 保留观察者回调列表。

  • 公开一个subscribe方法,观察者将调用该方法来订阅更改。该方法的返回值必须是一个函数,当调用时,该函数会将callback从 集合中移出subscriptions

  • 监听mouseMove事件并触发所有订阅回调。

代码如下所示:

class MousePositionObservable {
  constructor() {
    this.subscriptions = [];
    window.addEventListener('mousemove',this.handleMouseMove);
  }
  handleMouseMove =  (e) => {
     this.subscriptions.forEach(sub => sub(e.clientX, e.clientY));
  }
  subscribe(callback) {
    this.subscriptions.push(callback);    

    return () => {
      this.subscriptions = this.subscriptions.filter(cb => cb !== callback);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

步骤 2.创建 HTML 元素

circle我们现在为和创建 HTML 元素textMessageBox并为它们添加样式。

<div class="container">
  <div class="circle" ></div>
  <div class="mouse-position">
  <h4>Mouse Position</h4>
  <div class="position"></div>
</div>
</div>

Enter fullscreen mode Exit fullscreen mode
.container {
  position: relative;
  width: 100vw;
  height: 100vh;
  background-color: #f3df49;
}
.circle {
  position: absolute;
  background-color: #238643;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  z-index: 2;
}

.mouse-position {
  position: fixed;
  top: 20px;
  right: 20px;
  width: 200px;
  height: 100px;
  background-color: black;
  border-radius: 4px;
  padding: 4px 16px;
  color: white;
}

.mouse-position h4 {
  color: white;
  margin: 10px 0;
}
Enter fullscreen mode Exit fullscreen mode

步骤 3. 添加观察者

使其组合在一起的最后一步是创建我们的MousePositionObservable类的一个实例并向其添加观察者。

为此,我们将调用subscribe类实例上的方法并传递回调。

我们的代码如下所示:

const mousePositionObservable = new MousePositionObservable();

mousePositionObservable.subscribe((x, y) => {
  const circle = document.querySelector('.circle');
   window.setTimeout(() => {
     circle.style.transform = `translate(${x}px, ${y}px)`;
   }, 1000);
});

// Update the mouse positon container to show the mouse position values
mousePositionObservable.subscribe((x, y) => {
  const board = document.querySelector('.mouse-position .position');
  board.innerHTML = `
    <div>
       <div>ClientX: ${x}</div>
       <div>ClientY: ${y}</div>
    </div>
  `
})
Enter fullscreen mode Exit fullscreen mode

我们向实例添加两个订阅MousePositionObservable,每个需要监听鼠标值的元素一个。

元素的订阅回调circle会获取 DOM 元素的引用并更新其transform属性。transform 属性会尽可能使用硬件加速,因此,translate()如果元素上还使用了动画或过渡效果,则使用 top 和 left 位置会获得性能优势。

元素的订阅回调textbox使用该innerHTML属性更新其 HTML 内容。

注意:当你想取消订阅监听器时,你所要做的就是存储订阅函数调用的返回值,然后像函数一样调用它

这就是我们的演示所需要的全部内容。

您可以在下面的 Codepen 中查看工作示例:


观察者设计模式的优缺点

观察者设计模式给我们带来了以下好处:

  • 当我们想对单个事件执行多个操作时,它非常有用。

  • 它提供了一种解耦功能同时保持相关对象之间一致性的方法。

这种模式的缺点源于它的优点:

  • 由于观察者设计模式会导致代码松耦合,有时很难保证应用程序的其他部分正常工作。例如,添加到主题的订阅代码可能存在行为错误,但发布者却无法知晓。

实际应用

在进行 Web 开发时,我们看到了Redux基于React Context观察者设计模式的实现示例。

在 Redux 中,我们有一个subscribe方法可以向充当主题的 Redux 状态添加观察者。当 Redux Store 发生任何更改时,订阅该状态的观察者都会收到通知。

类似地,使用 React Context 时,每当 的值更新时,所有通过钩子或通过ContextProvider订阅 Context 的组件都将使用更新的上下文值重新渲染。useContextContext.Consumer


结论

在本文中,我们介绍了观察者设计模式以及如何在应用程序中使用它。我们还基于此模式实现了一个演示,并了解了采用这种方法设计交互的一些优缺点。

感谢您的阅读。

如果您发现这篇文章有用且内容丰富,请不要忘记点赞并与您的朋友和同事分享。

如果您有任何建议,请随时发表评论。

在Twitter上关注我以获取更多 Web 开发内容。

文章来源:https://dev.to/shubhamreacts/observer-design-pattern-in-javascript-74m
PREV
有助于前端 Web 开发的实用网站
NEXT
一些有用的 JavaScript 单行代码