发布订阅模式

138 阅读3分钟

一句话解释

他定义了对象间的一种一对多的依赖关系,当一个对象的状态发生变化的时候,所有依赖于他的对象都将得到通知

class Observer {
  /**
   * 三件事
   * 提供订阅功能
   * 提供触发功能
   * 提供取消订阅功能
   */

  obj = {};
  on(subscriberName, callback) {
    // 进行一个订阅,将所有传入的回调函数放到数组中去
    if (!this.obj[subscriberName]) {
      this.obj[subscriberName] = [callback];
    } else {
      this.obj[subscriberName].push(callback);
    }
  }

  trigger(subscriberName) {
	// 触发时将所有相关的函数运行一遍
    const subscribers = this.obj[subscriberName] || [];
    subscribers.forEach((sub) => sub());
  }

  remove(subscriberName, subscriber) {
    const subscribers = this.obj[subscriberName] || [];
    const index = subscribers.findIndex((el) => el === subscriber);
    if (index > -1) {
      subscribers.splice(index, 1);
    }
  }
}


// 创建Observer的实例
const bookStore = new Observer();

//定义一个回调函数
const listener = () => {
  console.log("用户1要去买书");
};

//绑定回调函数
bookStore.on("book", listener);

//其他实体的绑定
bookStore.on("book", () => {
  console.log("用户2要去买书");
});

//触发
bookStore.trigger("book");
//用户1要去买书
//用户2要去买书

//移除订阅
bookStore.remove("book", listener);

bookStore.trigger("book");
//用户2要去买书

vue的响应式使用发布订阅模式

首先vue想实现的功能是:

  • 某些数据改变的时候,使用该数据的函数全部自动运行一遍

这样的话,数据变化,就给dom树上的结点重新赋值,页面显示的数据就会自动更新

重写属性的get和set函数:

  • 所谓数据变化,就是发生了重新赋值,而我们在给函数重新赋值的时候实际上调用了set函数,
  • 所以只需要在set函数里顺便运行所有相关的函数就行了

现在的问题是: 我们如何让set函数知道有这些相关函数需要运行

相关函数收集

  • 我们可以使用get函数,考虑到每一个使用该属性的相关函数都至少会调用get函数,所以我们可以在它调用get函数的时候进行收集,也就是把这个函数的名字存起来,放进一个数组
  • 等到数据变化的时候对数组进行遍历运行,也就是在set函数中运行所有相关函数就行了

现在问题就只有一个:

  • 我们怎么让get函数知道谁在调用它好让它进行收集
  • 可以使用一个全局变量,在调用某个函数之前先把全局变量的值设置为这个函数名
  • 接下来运行函数,在函数运行过程中,函数中用到该属性的时候会调用get函数,其get函数这时候访问全局变量就可以得到是谁在调用它了
  • 这时候在get里进行函数收集就大功告成
  • 运行函数结束之后要将全局变量置为空

那么基于Object.defineProperty的响应式就得以实现

以下是observer函数的实现 用于观察对象的所有属性

/**
 * 观察某个对象的所有属性
 * @param {Object} obj
 */
function observe(obj) {
  for (const key in obj) {
    let internalValue = obj[key];
    let funcs = [];
    Object.defineProperty(obj, key, {
      get: function () {
        //  依赖收集,记录:是哪个函数在用我
        if (window.__func && !funcs.includes(window.__func)) {
          funcs.push(window.__func);
        }
        return internalValue;
      },
      set: function (val) {
        internalValue = val;
        // 派发更新,运行:执行用我的函数
        for (var i = 0; i < funcs.length; i++) {
          funcs[i]();
        }
      },
    });
  }
}

function autorun(fn) {
  window.__func = fn;
  fn();
  window.__func = null;
}

这是调用的示例

var user = {
  name: '杨柳',
  birth: '2002-5-7',
};

//调用obsever进行观察
observe(user); // 观察

// 显示姓氏
function showFirstName() {
  document.querySelector('#firstName').textContent = '姓:' + user.name[0];
}
// 显示名字
function showLastName() {
  document.querySelector('#lastName').textContent = '名:' + user.name.slice(1);
}

// autorun里面改变全局变量的值,方便get函数进行相关函数收集
autorun(showFirstName);
autorun(showLastName);