前言
- 前几天在学习
vue
的数据双向绑定,了解到“发布——订阅设计模式”,之前在vue的数据双向绑定上只是浅显的知道双向绑定靠的是原生 JS 中的Object.defineProperty()
中的set
和get
,而其中的具体实现却不是很了解。最近也通过学习才知道了其中的理念是靠“发布——订阅设计模式”这一设计思想来实现的,所以下面简单的实现以下,算是对自己对这方面知识的巩固,也加深自己的理解。
实现一个监听器(Observer)
- 顾名思义,监听器主要用来监听数据的改变或者数据的获取,这里就很显然要用到
Object.defineProperty()
中的set
和get
,大致代码如下:
const foo = {};
Object.defineProperty(foo, "value", {
get() {
console.log("这里监听到了获取数据");
},
set(newVal) {
console.log("这里监听到设置了新值");
},
});
复制代码
但是在 vue
的 data
方法返回的对象里面有很多的属性,所以接下来就需要循环遍历每个需要监听的对象属性,所以这个时候就需要一个函数方法做监听对象属性————这就是我们所要实现的监听器,即监听对象上每个属性数值,所以把上面的代码改造成这样:
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)
- 有订阅器就有订阅者,而订阅器的作用就是为了收集订阅者,因为订阅者可以有很多,而订阅器就一个,所以订阅器的作用就是方便管理所有的订阅者。这些订阅者订阅的东西,订阅器这边可以受理来自响应的监听器来通知订阅者发生改变了。
所以重点是最后一句话,即如下图所示:
所以订阅器 有两个功能:一个是收集订阅者(watcher),一个是执行通知变化,即代码如下:
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub(sub) {
this.subs.push(sub); // 收集订阅者
},
notify() {
this.subs.forEach((sub) => {
sub.update(); // 执行订阅者的更新操作
});
},
};
Dep.target = null;
复制代码
解释一下这串代码:
- 首先声明了
Dep
函数对象 - 接着在这个函数对象上声明了两个方法:
addSub
和notify
,分别是收集订阅者和执行通知变 - 最后
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