学习“发布——订阅设计模式”

前言

  • 前几天在学习 vue 的数据双向绑定,了解到“发布——订阅设计模式”,之前在vue的数据双向绑定上只是浅显的知道双向绑定靠的是原生 JS 中的Object.defineProperty() 中的setget,而其中的具体实现却不是很了解。最近也通过学习才知道了其中的理念是靠“发布——订阅设计模式”这一设计思想来实现的,所以下面简单的实现以下,算是对自己对这方面知识的巩固,也加深自己的理解。

实现一个监听器(Observer)

  • 顾名思义,监听器主要用来监听数据的改变或者数据的获取,这里就很显然要用到Object.defineProperty() 中的setget,大致代码如下:
const foo = {};
Object.defineProperty(foo, "value", {
  get() {
    console.log("这里监听到了获取数据");
  },
  set(newVal) {
    console.log("这里监听到设置了新值");
  },
});
复制代码

但是在 vuedata 方法返回的对象里面有很多的属性,所以接下来就需要循环遍历每个需要监听的对象属性,所以这个时候就需要一个函数方法做监听对象属性————这就是我们所要实现的监听器,即监听对象上每个属性数值,所以把上面的代码改造成这样:

function Observer(obj) {
  Object.keys(obj).forEach((key) => {
    Object.defineProperty(obj, key, {
      configurable: true,
      enumerable: true,
      writable: true,
      get() {
        console.log("这里监听到了获取数据");
      },
      set(newVal) {
        console.log("这里监听到设置了新值");
      },
    });
  });
}
复制代码

然后再调用监听器就可以了

const data = Observer({ name: "zhangsan", age: 18 });
复制代码

这样我们就实现了监听器。

实现一个订阅器(Dep)

  • 有订阅器就有订阅者,而订阅器的作用就是为了收集订阅者,因为订阅者可以有很多,而订阅器就一个,所以订阅器的作用就是方便管理所有的订阅者。这些订阅者订阅的东西,订阅器这边可以受理来自响应的监听器来通知订阅者发生改变了。

所以重点是最后一句话,即如下图所示:

1622689709(1).jpg

所以订阅器 有两个功能:一个是收集订阅者(watcher),一个是执行通知变化,即代码如下:

function Dep() {
  this.subs = [];
}
Dep.prototype = {
  addSub(sub) {
    this.subs.push(sub); // 收集订阅者
  },
  notify() {
    this.subs.forEach((sub) => {
      sub.update(); // 执行订阅者的更新操作
    });
  },
};
Dep.target = null;
复制代码

解释一下这串代码:

  • 首先声明了Dep函数对象
  • 接着在这个函数对象上声明了两个方法:addSubnotify ,分别是收集订阅者和执行通知变
  • 最后Dep.target = null 声明在全局,用于对存放当前的订阅者

然后有了订阅器,还需要改写一下监听器,改写有两个目的,一是为了将目订阅者者添加到订阅器中,而是为了再改变值的时候发出通知给订阅器

function Observer(obj) {
  const dep = new Dep();
  Object.keys(obj).forEach((key) => {
    Object.defineProperty(obj, key, {
      configurable: true,
      enumerable: true,
      writable: true,
      get() {
        console.log("这里监听到了获取数据");
        if(Dep.target != null) { // 判断当前是有有订阅者
            dep.addSub(Dep.target); // 有订阅者就执行添加到订阅者中
        }
      },
      set(newVal) {
        console.log("这里监听到设置了新值");
        dep.notify()
      },
    });
  });
}
复制代码

这样就创建好了订阅器,接下来就是如何利用订阅器,向订阅器中传入我们的订阅者。所以接下来就是创建订阅者。

实现一个订阅者(Watcher)

  • 如订阅器所描述的,我们需要关注订阅者的两件事,一是订阅者需要在接受到变更消息通知时,会执行一个更新操作。二是需要订阅者接受本身知道的数据,代码如下:
function Watcher() {}
Watcher.prototype = {
  update() {}, // 接受到消息后变更数据的方法
  get() {}, // 获取本身的实例的数据
};
复制代码

接下来重点关注如何去更新,如何拿到当前值。

  • 首先Watcher这个函数对象里面要声明更新实例、更新的节点属性、一个执行更新的回调方法和当前值,所以Watcher 函数里面就会有这些:
function Watcher(vm, exp, cb) {
  this.vm = vm;
  this.exp = exp;
  this.cb = cb;
  this.value = this.get(); // 获取当前的value
}
复制代码
  • 接下来我们看到 this.value = this.get() 这里调用了原型本身的getter,所以,接下来完善这个方法:
get() {
    Dep.target = this; // 表示在订阅器中执行的是当前watcher
    const val = this.vm.data[this.exp]; // 获取当前实例的值,即需要监听的值
    Dep.target = null; // 释放全局变量
    return val;
  }
复制代码
  • 最后需要执行更新操作的update()方法,即把新值替换旧值即可
update() {
    const newVal = this.vm.data[this.exp];
    const oldVal = this.value;
    if (newVal != oldVal) {
      this.cb(newVal);
    }
  }
复制代码

这样我们就实现了订阅者

总结

这样我们就简单实现了“发布——订阅”的设计模式,也就很好的理解了 vue 数据双向绑定的原理,笔者也是前端小白,还在努力研究vue的源码。通过每一小步学习,进一步慢慢了解了vue底层和思想。也慢慢加深了自己对原生JS的理解。

而这也只是“发布——订阅”设计模式的一部分,具体完整代码及对vue 数据双向绑定的源码分析请参考文献文章,笔者也是通过这篇文章带着拙劣的理解而写的,并多向大佬学习学习。

文献

[0 到 1 掌握:Vue 核心之数据双向绑定]juejin.cn/post/684490…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享