JavaScript设计模式:观察者模式与发布订阅模式深度解析
本文深入解析JavaScript中的观察者模式与发布订阅模式。详细阐述两者的核心角色、实现原理及优缺点,并通过生活案例与代码演示对比其区别。重点说明观察者模式是直接通信,而发布订阅模式通过中介者实现完全解耦,帮助开发者根据场景选择合适模式。
本文详细解析JavaScript中的观察者模式与发布订阅模式,旨在提供清晰的理论说明与代码参考。
在深入探讨设计模式的过程中,观察者模式与发布订阅模式因高度相似性常令人困惑。本文旨在厘清二者差异,开启Google探索之旅,并对学习过程进行系统梳理。
观察者模式
当对象间存在一对多依赖关系时,应采用观察者模式(Observer Pattern)。具体而言,当一个对象状态变更时,其所有依赖对象会自动收到通知。该模式属于行为型设计模式。在观察者模式中,主要涉及两个核心角色观察者(Observer)与被观察者(Subject)。在软件设计中,观察者模式表现为一个对象(目标)维护一个依赖列表,并在状态发生变化时自动通知列表中的所有观察者。
实质上,观察者模式是指一个或多个观察者对目标对象的状态保持关注。观察者通过依附于目标对象来注册其感兴趣的状态。当目标状态发生变动且观察者对此感兴趣时,目标会发送通知消息,并调用每个观察者的更新方法。若观察者不再关注目标状态,可自行从依赖列表中移除。
在观察者模式中,主要包含以下角色:
- Subject(目标):维护观察者列表,支持添加或删除操作。
- Observer(观察者):为需要接收目标状态变更通知的对象提供更新接口。
- ConcreteSubject(具体目标):当状态改变时,向Observer发送通知,并存储ConcreteObserver的状态。
- ConcreteObserver(具体观察者):实现具体的观察逻辑。
举例
以职场场景为例,公司老板可向员工分配任务。若将老板视为被观察者,则下属员工即为观察者。老板通过分配任务这一行为,通知员工执行相应工作。
基于上述案例,结合图示进一步分析其关系结构。
若Subject = 老板,则Observer N = 工作人员。仔细观察图示会发现多出一个notify()方法,该示例中的核心工作流即为notify()的执行。

理清关系后,通过代码实现上述案例:
// 观察者队列
class ObserverList{
constructor(){
this.observerList = {};
}
Add(obj,type = "any"){
if(!this.observerList[type]){
this.observerList[type] = [];
}
this.observerList[type].push(obj);
}
Count(type = "any"){
return this.observerList[type].length;
}
Get(index,type = "any"){
let len = this.observerList[type].length;
if(index > -1 && index < len){
return this.observerList[type][index]
}
}
IndexOf(obj,startIndex,type = "any"){
let i = startIndex,
pointer = -1;
let len = this.observerList[type].length;
while(i < len){
if(this.observerList[type][i] === obj){
pointer = i;
}
i++;
}
return pointer;
}
RemoveIndexAt(index,type = "any"){
let len = this.observerList[type].length;
if(index === 0){
this.observerList[type].shift();
}
else if(index === len-1){
this.observerList[type].pop();
}
else{
this.observerList[type].splice(index,1);
}
}
}
// 老板
class Boos {
constructor(){
this.observers = new ObserverList();
}
AddObserverList(observer,type){
this.observers.Add(observer,type);
}
RemoveObserver(oberver,type){
let i = this.observers.IndexOf(oberver,0,type);
(i != -1) && this.observers.RemoveIndexAt(i,type);
}
Notify(type){
let oberverCont = this.observers.Count(type);
for(let i=0;i<oberverCont;i++){
let emp = this.observers.Get(i,type);
emp && emp(type);
}
}
}
class Employees {
constructor(name){
this.name = name;
}
getName(){
return this.name;
}
}
class Work {
married(name){
console.log(`${name}上班`);
}
unemployment(name){
console.log(`${name}出差`);
}
writing(name){
console.log(`${name}写作`);
}
writeCode(name){
console.log(`${name}打代码`);
}
}
let MyBoos = new Boos();
let work = new Work();
let aaron = new Employees("Aaron");
let angie = new Employees("Angie");
let aaronName = aaron.getName();
let angieName = angie.getName();
MyBoos.AddObserverList(work.married,aaronName);
MyBoos.AddObserverList(work.writeCode,aaronName);
MyBoos.AddObserverList(work.writing,aaronName);
MyBoos.RemoveObserver(work.writing,aaronName);
MyBoos.Notify(aaronName);
MyBoos.AddObserverList(work.married,angieName);
MyBoos.AddObserverList(work.unemployment,angieName);
MyBoos.Notify(angieName);
// Aaron上班
// Aaron打代码
// Angie上班
// Angie出差
代码严格遵循流程图逻辑,Boos类作为被观察者,Staff作为观察者,通过Work模块建立关联。
阅读代码可知,观察者模式的核心在于peopleList对象。该对象内部存储了N多个Array数组,并通过run方法触发观察者的notify队列。观察者模式主要解决对象状态变更时对其他对象的通知问题,旨在实现易用性与低耦合,确保高度协作。当目标对象状态改变需进行广播通知时,即可采用此模式。
优点
- 观察者和被观察者之间保持抽象耦合。
- 建立了一套触发机制。
缺点
- 若被观察者拥有大量直接及间接观察者,通知所有观察者将消耗大量时间。
- 若观察者和观察目标之间存在循环依赖,目标触发调用可能导致系统崩溃。
- 观察者模式缺乏机制让观察者知晓目标对象的具体变化原因,仅能感知到状态已变更。
小结
在观察者模式中,被观察者内部维护一个用于存储观察者对象的list队列,并通过统一方法触发通知。目标和观察者为基类,目标提供维护观察者的方法,观察者提供更新接口。具体观察者和具体目标继承各自基类,具体观察者注册到具体目标中。当具体目标状态变化时,调度观察者的更新方法。
发布/订阅模式
在理解发布订阅模式时曾遇到较大困难,经过大量Google查阅资料后逐渐理清思路。理解该模式的关键在于明确发布者与订阅者的角色定位。
发布订阅:在软件架构中,发布-订阅是一种消息范式。消息发送者(发布者)不直接将消息发送给特定接收者(订阅者),而是将消息分类,无需知晓存在哪些订阅者。订阅者可表达对一个或多个类别的兴趣,仅接收感兴趣的消息,无需知晓存在哪些发布者。-- 维基百科
初看概念可能感到困惑,但学习需持续深入。
许多人误以为发布订阅模式中的Publisher等同于观察者模式中的Subject,而Subscriber等同于Observer。当Publisher变化时,直接通知Subscriber。然而事实并非如此。在发布订阅模式中,发布者并不直接通知订阅者,二者互不相识。那么它们如何交互?
答案是通过第三者,即消息队列中的经纪人Broker。
发布者只需告知Broker,其发送的消息topic为AAA;订阅者只需告知Broker,其订阅topic为AAA的消息。当Broker收到发布者消息且topic为AAA时,会将消息推送给订阅了topic为AAA的订阅者。具体实现中,也可能是订阅者主动拉取消息。
由此可见,在发布订阅模式中,发布者和订阅者并非仅松耦合,而是实现了完全解耦。

基于上述描述,再次通过生活案例说明:以微信公众号为例,公众号推送消息时并非发送给所有微信用户,而是有选择地推送给已订阅该公众号的用户。
接下来通过代码实现该模式:
class Utils {
constructor(){
this.observerList = {};
}
Add(obj,type = "any"){
if(!this.observerList[type]){
this.observerList[type] = [];
}
this.observerList[type].push(obj);
}
Count(type = "any"){
return this.observerList[type].length;
}
Get(index,type = "any"){
let len = this.observerList[type].length;
if(index > -1 && index < len){
return this.observerList[type][index]
}
}
IndexOf(obj,startIndex,type = "any"){
let i = startIndex,
pointer = -1;
let len = this.observerList[type].length;
while(i < len){
if(this.observerList[type][i] === obj){
pointer = i;
}
i++;
}
return pointer;
}
}
// 订阅者
class Subscribe extends Utils {};
// 发布者
class Publish extends Utils {};
// 中转站
class Broker {
constructor(){
this.publish = new Publish();
this.subscribe = new Subscribe();
}
// 订阅
Subscribe(fn,key){
this.subscribe.Add(fn,key);
}
// 发布
Release(fn,key){
this.publish.Add(fn,key);
}
Run(key = "any"){
let publishList = this.publish.observerList;
let subscribeList = this.subscribe.observerList;
if(!publishList[key] || !subscribeList[key]) throw "No subscribers or published messages";
let pub = publishList[key];
let sub = subscribeList[key];
let arr = [...pub,...sub];
while(arr.length){
let item = arr.shift();
item(key);
}
}
}
class Employees {
constructor(name){
this.name = name;
}
getName(){
let {name} = this;
return name;
}
receivedMessage(key,name){
console.log(`${name}收到了${key}发来的消息`);
}
}
class Public {
constructor(name){
this.name = name;
}
getName(){
let {name} = this;
return name;
}
sendMessage(key){
console.log(`${key}发送了一条消息`);
}
}
let broker = new Broker();
let SundayPublic = new Public("Sunday");
let MayPublic = new Public("May");
let Angie = new Employees("Angie");
let Aaron = new Employees("Aaron");
broker.Subscribe(() => {
Angie.receivedMessage(SundayPublic.getName(),Angie.getName());
},SundayPublic.getName());
broker.Subscribe(() => {
Angie.receivedMessage(SundayPublic.getName(),Aaron.getName());
},SundayPublic.getName());
broker.Subscribe(() => {
Aaron.receivedMessage(MayPublic.getName(),Aaron.getName());
},MayPublic.getName());
broker.Release(MayPublic.sendMessage,MayPublic.getName());
broker.Release(SundayPublic.sendMessage,SundayPublic.getName());
broker.Run(SundayPublic.getName());
broker.Run(MayPublic.getName());
// Sunday发送了一条消息
// Angie收到了Sunday发来的消息
// Aaron收到了Sunday发来的消息
// May发送了一条消息
// Aaron收到了May发来的消息
通过输出结果可知,只要用户订阅了特定公众号,当公众号发送消息时,所有订阅该消息的用户均可收到。尽管代码量较多,但确实体现了发布订阅模式的魅力。
优点
- 支持简单的广播通信,当对象状态改变时,自动通知已订阅对象。
- 发布者与订阅者耦合性降低。发布者仅负责发布消息,不关心消息如何被使用;订阅者仅监听事件名,不关心发布者内部实现。例如,卖家(发布者)只需通知订阅者(买家)鞋子到货,无论买家是否购买或购买哪家产品,只要到货即通知。
缺点
- 创建订阅者需消耗一定时间和内存。
- 虽然能弱化对象间联系,但过度使用可能导致代码难以理解及维护。
小结
发布订阅模式降低了发布者与订阅者之间的耦合程度,二者互不关心对方身份。订阅者跟随发布者,若发布者变化则通知订阅者做出相应调整。两者间无直接通信,所有交互通过中介者完成,中介者过滤并分发消息。该模式适用于不同系统组件间传递消息,且组件无需知晓彼此身份。
观察者模式与发布订阅的区别
- 在
Observer模式中,Observers知晓Subject,且Subject保留了Observers的记录。而在发布/订阅模式中,发布者和订阅者无需彼此了解,仅通过消息队列或代理通信。 - 在
Publisher / Subscriber模式中,组件是松散耦合的,而非Observer模式。 - 观察者模式主要同步实现,即事件发生时,
Subject调用所有观察者的相应方法。发布/订阅模式大多异步实现(使用消息队列)。 - 观察者模式需在单个应用程序地址空间内实现。相比之下,发布/订阅模式更适用于跨应用程序场景。

从结构看,发布订阅模式比观察者模式多出一个中间件订阅器,因此二者不同。从意图看,二者均实现对象间的一对多依赖关系:当一个对象状态改变,所有依赖对象均获通知并自动更新,此时可视为同一种模式,发布订阅模式是观察者模式的优化升级。在观察者模式中,观察者直接订阅目标事件,目标发出变更事件后直接接收并响应。发布订阅模式多了事件通道,订阅者与发布者非直接关联。目标和观察者直接联系,观察者添加至目标对象中,与发布订阅模式差异显著。在此模式下,目标更像发布者,执行所有添加的观察者函数,而观察者像订阅者。尽管两种模式均有订阅者和发布者(具体观察者可视作订阅者,具体目标可视作发布者),但观察者模式由具体目标调度,而发布/订阅模式由统一调度中心调度。因此,观察者模式的订阅者与发布者间存在依赖,而发布/订阅模式则无此依赖。
总结
尽管学习这两种模式过程坎坷,但最终按理解编写了两个案例。若理解有偏差或存在错误,欢迎留言指正,将尽快修复。
感兴趣的朋友可使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
