前言
此为自己学习vue源码响应式部分的一个小demo,按照自己的理解实现改变变量a,变量b也跟着改变的效果,整体实现较为简单,不考虑任何复杂场景,如有问题请大家指正
效果
data.age改变后,comouted.computedB的值也改变了
node index.js
// hello, computedb is 22
// hello, computedb is 59
代码
可以直接复制到浏览器控制台运行
// 发布者
class Observer {
constructor() {
this.listeners = [];
}
collect(t) {
if (t) {
this.listeners.push(t);
}
}
publish() {
this.listeners.forEach((item) => {
item();
});
}
}
// 订阅者
class Watcher {
constructor(v) {
this.val = v;
}
}
// 初始化方法,把数据变成一个 发布者
function becomeObserver(obj) {
Object.keys(obj).forEach((item) => {
const ob = new Observer();
let val = obj[item];
Object.defineProperty(obj, item, {
get() {
// 收集订阅者
ob.collect(
currentWatcher && currentWatcher.val ? currentWatcher.val : ""
);
return val;
},
set(newV) {
val = newV;
// 通知订阅者
ob.publish();
},
});
});
}
// 订阅方法, 把comouted 、watcher 等变为一个订阅者
function becomeWatcher(obj) {
Object.keys(obj).forEach((item) => {
const val = obj[item];
Object.defineProperty(obj, item, {
get() {
// 把自己这个watcher放到对应的发布者的listeners里
let pos = watcherList.findIndex((item) => item.val === val);
if (pos !== -1) {
currentWatcher = watcherList[pos];
} else {
watcherList.push(new Watcher(val));
currentWatcher = watcherList[watcherList.length - 1];
}
return val;
},
});
});
}
// 记录所有的订阅者
let watcherList = [];
// 记录当前正在使用的订阅者
let currentWatcher = null;
// data属性 ,对应vue组件中的data
var data = {
age: 2,
};
// 对应vue组件里的comouted属性
var computed = {
computedB() {
const val = data.age + 20;
console.log("hello, computedb is", val);
return val;
},
};
// 初始化data
becomeObserver(data);
// 初始化computed
becomeWatcher(computed);
// computed 调用, 模仿在vue 中使用computed属性
computed.computedB();
data.age = 39;
说明
我的理解:vue 的响应式就是一个发布订阅模式,发布者是 data,订阅者是 computed、watch、组件…,每次 data 改变,相应的 computed、watch 都会执行,页面也会刷新(组件执行)。
关键点1: 发布订阅双方是谁?
- 发布者:data
- 订阅者:computed、watch、组件
关键点2:发布者需要做两个操作,收集订阅者、通知订阅者,在哪里做这两个操作?
根据目前我们对 vue 的了解,都知道是在 get 的时候收集订阅者,在 set 的时候通知订阅者
关键点3:每个 comouted、watch 的属性都是一个订阅者,那个属性 a 在 get 的时候该收集哪一个呢?
哪个订阅者调用了 a,那么 a 就该收集该订阅者。
关键点4:属性 a 在 get 的时候,怎么知道是订阅者 b 调用了自己
订阅者 b 在调用属性 a 的时候(也就是在执行订阅者 b 的 get 的时候)需要把自己保存在全局变量 currentWatcher 中,那么属性 a 可以通过 currentWatcher 拿到订阅者 b 了
关键点5: 订阅者 b 是什么时候放到全局的 currentWacther 里的
从上面也可以看出是订阅者 b 在被使用的时候(get 的时候)记录到 currentWacther 的
关键点6:代码里的 watcherList 是干嘛的
订阅者 b 不能 get 一次就生成一个新的 Watcher,最终导致重复执行 comouted 的某个属性。所要用 watcherList 来记录已经被变成订阅者的 comouted、watche 等的属性。