前端中的订阅发布模式

  • 定义
  • 观察者模式
  • 手写实现
  • vue中如何利用订阅发布模式实现数据双向绑定
  • vue中的$on, $emit eventBus等
  • Promise

定义

订阅发布模式(Publish-Subscribe Pattern),又称观察者模式(Observer Pattern),是一种常用的设计模式,用于解决多个对象间的依赖关系,降低系统耦合度,提高代码的复用性和可维护性。

发布订阅是一种消息范式,发布者不会将消息直接发送给订阅者,而是将发布的消息分为不同的类别,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在。

在订阅发布模式中,存在两类角色:发布者和订阅者。发布者负责向订阅者发送消息,而订阅者则负责接收消息并做出相应的处理。发布者和订阅者之间通过一个中介者(通常称为消息队列或事件总线)来进行通信,发布者发布消息后,消息将被发送到中介者,中介者再将消息分发给所有订阅者。

具体实现上,订阅者需要先订阅发布者感兴趣的事件或主题(Topic),这样才能接收到相应的消息。当发布者发布与该主题相关的消息时,所有订阅该主题的订阅者将收到该消息,并进行相应的处理。


观察者模式

观察者模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态,观察者模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步。

区别

订阅发布模式和观察者模式都是常见的设计模式,它们的作用都是在对象之间建立松耦合的联系,实现对象间的通信。不过两者之间也有不同之处:

  1. 观察者模式中,被观察者(也叫主题)维护了一个观察者列表,当主题状态发生变化时,会遍历观察者列表,依次通知观察者执行相应的方法,实现对象间的通信。而订阅发布模式中,则是通过一个事件中心来协调发布者和订阅者之间的关系。发布者负责发布事件,订阅者则订阅事件,当事件触发时,订阅者就会收到通知。

  2. 观察者模式中,观察者需要直接订阅被观察者,这会造成双方之间的耦合。而订阅发布模式中,发布者和订阅者之间不需要直接联系,通过事件中心来进行交互,实现了更加松散的耦合。

  3. 在观察者模式中,被观察者和观察者之间是一对多的关系,一个被观察者可以有多个观察者。而在订阅发布模式中,发布者和订阅者之间是多对多的关系,一个发布者可以有多个订阅者,一个订阅者也可以订阅多个发布者。

  4. 在观察者模式中,观察者收到通知后,必须按照被观察者规定的方法进行操作,这限制了观察者的自由度。而在订阅发布模式中,发布者只需要发布事件,订阅者则可以根据自己的需求进行操作,实现了更加灵活的设计。

总的来说,观察者模式适合在一对多的关系中使用,比如 GUI 控件和模型之间的关系,而订阅发布模式则适用于多对多的关系,比如消息通知、事件驱动等场景。


手写实现

class PubSub {
  constructor() {
    this.subscribers = {};
  }

  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }
    this.subscribers[event].push(callback);
  }

  publish(event, data) {
    if (!this.subscribers[event]) {
      return;
    }
    this.subscribers[event].forEach((callback) => callback(data));
  }
}

// 使用示例
const pubsub = new PubSub();

// 订阅事件
pubsub.subscribe('myEvent', (data) => {
  console.log('收到 myEvent 事件', data);
});

// 发布事件
pubsub.publish('myEvent', { message: 'Hello, world!' });

在这个例子中,我们定义了一个 PubSub 类,它有两个方法 subscribe 和 publish。subscribe 方法用于订阅事件,接受两个参数,第一个是事件名称,第二个是回调函数,该回调函数会在事件发生时被调用。publish 方法用于发布事件,接受两个参数,第一个是事件名称,第二个是事件数据,它会调用所有订阅该事件的回调函数,并将事件数据作为参数传递给它们。

在上面的示例中,我们首先创建了一个 PubSub 实例,并使用 subscribe 方法订阅了一个名为 myEvent 的事件。接着,我们使用 publish 方法发布了该事件,并传递了一个包含消息的数据对象。在事件发生时,subscribe 方法中定义的回调函数会被调用,打印出事件数据。


vue中的双向数据绑定

vue通过数据劫持和订阅发布模式实现了双向数据绑定。具体来说,当一个 Vue 实例创建时,Vue 会遍历 data 选项中的所有属性,使用 Object.defineProperty 将它们转化为 getter/setter,并且在内部建立依赖收集的关系。

当数据发生变化时,Vue 会通过发布-订阅模式通知所有相关的观察者(Watcher),让它们更新视图。Vue 中的观察者实现了一个 update() 方法,在被通知更新时会调用这个方法去更新对应的视图。

在视图中,当用户对数据进行修改时,Vue 会利用 v-model 指令实现双向数据绑定。具体来说,v-model 会监听 input 或者其他表单元素的 input 或者 change 事件,然后通过事件回调函数将修改后的值赋值给对应的 data 属性。

因此,Vue 的双向数据绑定的实现原理可以概括为以下几个步骤:

  1. 遍历 data 选项中的属性,利用 Object.defineProperty 转化为 getter/setter,并在内部建立依赖收集的关系;
  2. 当数据发生变化时,Vue 会通知所有相关的观察者(Watcher)更新视图;
  3. 在视图中,v-model 指令会监听表单元素的 input 或者 change 事件,并通过事件回调函数将修改后的值赋值给对应的 data 属性。

通过以上步骤,Vue 实现了数据的双向绑定,让开发者可以方便地实现视图与数据的同步更新。


vue中的$on和$emit

在Vue中,$on和$emit是实现订阅发布模式的核心方法。$on用于订阅事件,$emit用于触发事件。

  1. $on的实现:
    当执行vm.$on(eventName, callback)时,会执行以下代码:
if (Array.isArray(eventName)) {
  for (let i = 0, l = eventName.length; i < l; i++) {
    this.$on(eventName[i], callback)
  }
} else {
  (this._events[eventName] || (this._events[eventName] = [])).push(callback)
}

可以看到,$on方法内部实现了事件订阅的逻辑。首先判断传入的eventName是否为数组,如果是,则递归调用$on方法,将每一个eventName和callback分别传入,实现一次性订阅多个事件的功能。如果eventName不是数组,则将callback加入到_events对象的eventName属性值对应的数组中。

$emit的实现:
当执行vm.$emit(eventName, arg1, arg2, …)时,会执行以下代码:

let cbs = this._events[eventName]
if (cbs) {
  cbs = cbs.length > 1 ? toArray(cbs) : cbs
  const args = toArray(arguments, 1)
  for (let i = 0, l = cbs.length; i < l; i++) {
    cbs[i].apply(this, args)
  }
}

可以看到,$emit方法内部实现了事件发布的逻辑。首先从_events对象中获取eventName对应的回调函数数组cbs,如果存在cbs,则将其转换为数组形式。然后,将除eventName外的其他参数组成一个数组args。最后,遍历cbs数组,依次执行每一个回调函数,并将args作为参数传入。

可以看出,$on和$emit的实现都是通过对_events对象的处理来实现的,也就是说,Vue中的$on和$emit确实是使用了订阅发布模式。


Promise

Promise 内部使用发布订阅模式来处理异步操作的状态变化。

在 Promise 内部,有三种状态:pending(等待中)、fulfilled(已成功)和 rejected(已失败)。当状态发生变化时,Promise 对象会发布对应的事件,通知所有注册的回调函数。这些回调函数就是订阅者。

当 Promise 对象处于 pending 状态时,它会维护一个回调函数列表。当 Promise 对象的状态发生变化时,会依次执行这个回调函数列表中的函数。这些回调函数就是发布者。

例如,当 Promise 对象状态变为 fulfilled(已成功)时,它会执行 resolve 函数,并向所有订阅了该 Promise 对象的回调函数传递成功的结果。这个过程中,Promise 内部就使用了发布订阅模式。

下面是一个示例代码,用于演示 Promise 内部使用发布订阅模式的过程:

class MyPromise {
  constructor() {
    this.state = 'pending';
    this.callbacks = [];
  }

  resolve(result) {
    this.state = 'fulfilled';
    this.result = result;
    this.callbacks.forEach(callback => callback(this.result));
  }

  reject(error) {
    this.state = 'rejected';
    this.error = error;
    this.callbacks.forEach(callback => callback(this.error));
  }

  then(callback) {
    if (this.state === 'fulfilled') {
      callback(this.result);
    } else if (this.state === 'pending') {
      this.callbacks.push(callback);
    }
  }
}

const promise = new MyPromise();

promise.then(result => console.log(result));

promise.resolve('Hello World!');

在上面的示例代码中,MyPromise 类实现了一个简单的 Promise 对象。它维护了 Promise 对象的状态和回调函数列表,当 Promise 对象状态发生变化时,会执行对应的回调函数列表中的函数。在 then 方法中,如果 Promise 对象已经处于 fulfilled 状态,直接执行回调函数;如果处于 pending 状态,将回调函数添加到回调函数列表中。

在执行 promise.resolve(‘Hello World!’); 时,Promise 对象状态变为 fulfilled,并执行回调函数列表中的回调函数,输出结果 Hello World!。在这个过程中,Promise 内部使用了发布订阅模式来处理状态变化和回调函数的执行。

扩展:
promise实现发布订阅模式

具体实现方式是,使用 Promise 的 then 方法来订阅事件,当 Promise 状态改变时,会自动触发 then 方法中注册的回调函数。而使用 Promise 的 resolve 方法来触发事件,resolve 方法会将 Promise 的状态改变为 resolved,从而触发 then 方法中注册的回调函数。

举个例子,我们可以创建一个名为 EventBus 的类,内部包含一个 Promise 对象:

class EventBus {
  constructor() {
    this.promise = Promise.resolve();
  }
  
  publish(event, data) {
    this.promise = this.promise.then(() => {
      const handlers = this.events[event] || [];
      handlers.forEach(handler => handler(data));
    });
  }
  
  subscribe(event, handler) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(handler);
  }
  
  unsubscribe(event, handler) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(h => h !== handler);
    }
  }
}

在上面的代码中,我们定义了一个 EventBus 类,包含了三个方法:publish、subscribe 和 unsubscribe。其中,subscribe 方法用来订阅事件,unsubscribe 方法用来取消订阅事件,而 publish 方法用来触发事件。

具体来说,publish 方法会将事件名和数据作为参数,然后将其包装成一个 Promise,并将其添加到 promise 链中。在添加到链中之前,会先执行链中的所有前置任务(即在该任务之前添加到链中的所有任务),以保证事件的触发顺序。

而 subscribe 方法则会将事件名和回调函数添加到 this.events 对象中,而 unsubscribe 方法则会将指定的回调函数从事件列表中删除。

下面是一个使用 EventBus 的例子:

const eventBus = new EventBus();

eventBus.subscribe('event1', data => {
  console.log(`Event1: ${data}`);
});

eventBus.subscribe('event2', data => {
  console.log(`Event2: ${data}`);
});

eventBus.publish('event1', 'Hello, world!');
eventBus.publish('event2', 'Hi there!');

在上面的代码中,我们首先创建了一个 EventBus 对象,然后分别订阅了 event1 和 event2 事件,并分别传入了回调函数。最后,我们通过 publish 方法来触发这两个事件,并传入了不同的数据。当事件触发时,会自动调用对应的回调函数,并将数据作为参数传入。

文章中若有不恰当的地方或者有什么需要改进的地方,可以在下方评论,感谢

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值