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);
}
}
}
步骤 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>
.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;
}
步骤 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>
`
})
我们向实例添加两个订阅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 的组件都将使用更新的上下文值重新渲染。useContext
Context.Consumer
结论
在本文中,我们介绍了观察者设计模式以及如何在应用程序中使用它。我们还基于此模式实现了一个演示,并了解了采用这种方法设计交互的一些优缺点。
感谢您的阅读。
如果您发现这篇文章有用且内容丰富,请不要忘记点赞并与您的朋友和同事分享。
如果您有任何建议,请随时发表评论。
在Twitter上关注我以获取更多 Web 开发内容。
文章来源:https://dev.to/shubhamreacts/observer-design-pattern-in-javascript-74m