Tian Jiale's Blog

JavaScript 实现发布订阅

事由

在开发微信小程序的过程中,因为要维护全局用户数据,所以各个页面都要从 app.js 同步用户数据;另外在一些其他的全局数据中也需要使用此功能。

在微信开发文档中只有通过 app.globalData 实现数据获取全局数据的方法。因为全局数据是从后端获取的,所以页面加载过程中不知晓全局数据什么时候准备好,所以在同步数据过程中只能延时获取,但该方法时间长了影响用户体验,时间短了并不能获取到数据;还可以循环调用直到数据完成同步,但这种方法需要检测数据内容,对数据有强依赖。

在今天查找解决方案的时候,才发现 JavaScript 中发布订阅的使用方法。

代码实现

class Event {
  /** on 方法把订阅者所想要订阅的事件及相应的回调函数记录在 Event 对象的 cbs 属性中并调用 */
  on(event, fn) {
    if (typeof fn !== 'function') {
      // eslint-disable-next-line no-console
      console.error('fn must be a function');
      return;
    }
    this.cbs = this.cbs || {};
    this.data = this.data || {};
    (this.cbs[event] = this.cbs[event] || []).push(fn);
    if (this.data[event]) {
      const callbacks = this.cbs[event];
      callbacks?.forEach((callback) => {
        callback(this.data[event]);
      });
    }
  }

  /** emit 方法接受一个事件名称参数,在 Event 对象的 cbs 属性中取出对应的数组,并逐个执行里面的回调函数 */
  emit(event, data) {
    this.cbs = this.cbs || {};
    this.data = this.data || {};
    this.data[event] = data;
    const callbacks = this.cbs[event];
    callbacks?.forEach((callback) => {
      callback(data);
    });
  }

  /** off 方法接受事件名称和当初注册的回调函数作参数,在 Event 对象的 cbs 属性中删除对应的回调函数。 */
  off(event, fn) {
    this.cbs = this.cbs || {};
    this.data = this.data || {};
    // all
    if (!arguments.length) {
      this.cbs = {};
      return;
    }
    const callbacks = this.cbs[event];
    if (!callbacks) return;
    const data = this.data[event];
    if (!data) return;
    // remove all handlers
    if (arguments.length === 1) {
      delete this.cbs[event];
      delete this.data[event];
      return;
    }
    // remove specific handler
    let cb;
    for (let i = 0, len = callbacks.length; i < len; i += 1) {
      cb = callbacks[i];
      if (cb === fn || cb.fn === fn) {
        callbacks.splice(i, 1);
        break;
      }
    }
  }
}
export default Event;